diff --git a/README.md b/README.md index a32d437a7..b7375f742 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ a method of another library isn't working as an iterator, study this example: // Here is a simple object with an (unnecessarily roundabout) squaring method var AsyncSquaringLibrary = { squareExponent: 2, - square: function(number, callback){ + square: function(number, callback){ var result = Math.pow(number, this.squareExponent); setTimeout(function(){ callback(null, result); @@ -71,7 +71,7 @@ async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){ // result is [1, 4, 9] // With the help of bind we can attach a context to the iterator before - // passing it to async. Now the square function will be executed in its + // passing it to async. Now the square function will be executed in its // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent` // will be as expected. }); @@ -89,7 +89,7 @@ __Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async ## In the Browser -So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. +So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: @@ -160,6 +160,8 @@ Usage: * [`log`](#log) * [`dir`](#dir) * [`noConflict`](#noConflict) +* [`stackSafe`](#stackSafe) +* [`wrapStackSafe`](#wrapStackSafe) ## Collections @@ -180,8 +182,8 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A function to apply to each item in `arr`. - The iterator is passed a `callback(err)` which must be called once it has - completed. If no error has occured, the `callback` should be run without + The iterator is passed a `callback(err)` which must be called once it has + completed. If no error has occured, the `callback` should be run without arguments or with an explicit `null` argument. * `callback(err)` - A callback which is called when all `iterator` functions have finished, or an error occurs. @@ -199,13 +201,13 @@ async.each(openFiles, saveFile, function(err){ ``` ```js -// assuming openFiles is an array of file names +// assuming openFiles is an array of file names async.each(openFiles, function( file, callback) { - + // Perform operation on file here. console.log('Processing file ' + file); - + if( file.length > 32 ) { console.log('This file name is too long'); callback('File name too long'); @@ -233,7 +235,7 @@ async.each(openFiles, function( file, callback) { ### eachSeries(arr, iterator, callback) The same as [`each`](#each), only `iterator` is applied to each item in `arr` in -series. The next `iterator` is only called once the current one has completed. +series. The next `iterator` is only called once the current one has completed. This means the `iterator` functions will complete in order. @@ -243,10 +245,10 @@ This means the `iterator` functions will complete in order. ### eachLimit(arr, limit, iterator, callback) -The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously +The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously running at any time. -Note that the items in `arr` are not processed in batches, so there is no guarantee that +Note that the items in `arr` are not processed in batches, so there is no guarantee that the first `limit` `iterator` functions will complete before any others are started. __Arguments__ @@ -254,8 +256,8 @@ __Arguments__ * `arr` - An array to iterate over. * `limit` - The maximum number of `iterator`s to run at any time. * `iterator(item, callback)` - A function to apply to each item in `arr`. - The iterator is passed a `callback(err)` which must be called once it has - completed. If no error has occured, the callback should be run without + The iterator is passed a `callback(err)` which must be called once it has + completed. If no error has occured, the callback should be run without arguments or with an explicit `null` argument. * `callback(err)` - A callback which is called when all `iterator` functions have finished, or an error occurs. @@ -278,19 +280,19 @@ async.eachLimit(documents, 20, requestApi, function(err){ Produces a new array of values by mapping each value in `arr` through the `iterator` function. The `iterator` is called with an item from `arr` and a -callback for when it has finished processing. Each of these callback takes 2 arguments: -an `error`, and the transformed item from `arr`. If `iterator` passes an error to this +callback for when it has finished processing. Each of these callback takes 2 arguments: +an `error`, and the transformed item from `arr`. If `iterator` passes an error to this callback, the main `callback` (for the `map` function) is immediately called with the error. Note, that since this function applies the `iterator` to each item in parallel, -there is no guarantee that the `iterator` functions will complete in order. +there is no guarantee that the `iterator` functions will complete in order. However, the results array will be in the same order as the original `arr`. __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A function to apply to each item in `arr`. - The iterator is passed a `callback(err, transformed)` which must be called once + The iterator is passed a `callback(err, transformed)` which must be called once it has completed with an error (which can be `null`) and a transformed item. * `callback(err, results)` - A callback which is called when all `iterator` functions have finished, or an error occurs. Results is an array of the @@ -310,7 +312,7 @@ async.map(['file1','file2','file3'], fs.stat, function(err, results){ ### mapSeries(arr, iterator, callback) The same as [`map`](#map), only the `iterator` is applied to each item in `arr` in -series. The next `iterator` is only called once the current one has completed. +series. The next `iterator` is only called once the current one has completed. The results array will be in the same order as the original. @@ -319,10 +321,10 @@ The results array will be in the same order as the original. ### mapLimit(arr, limit, iterator, callback) -The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously +The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously running at any time. -Note that the items are not processed in batches, so there is no guarantee that +Note that the items are not processed in batches, so there is no guarantee that the first `limit` `iterator` functions will complete before any others are started. __Arguments__ @@ -330,7 +332,7 @@ __Arguments__ * `arr` - An array to iterate over. * `limit` - The maximum number of `iterator`s to run at any time. * `iterator(item, callback)` - A function to apply to each item in `arr`. - The iterator is passed a `callback(err, transformed)` which must be called once + The iterator is passed a `callback(err, transformed)` which must be called once it has completed with an error (which can be `null`) and a transformed item. * `callback(err, results)` - A callback which is called when all `iterator` calls have finished, or an error occurs. The result is an array of the @@ -363,7 +365,7 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A truth test to apply to each item in `arr`. - The `iterator` is passed a `callback(truthValue)`, which must be called with a + The `iterator` is passed a `callback(truthValue)`, which must be called with a boolean argument once it has completed. * `callback(results)` - A callback which is called after all the `iterator` functions have finished. @@ -385,7 +387,7 @@ async.filter(['file1','file2','file3'], fs.exists, function(results){ __Alias:__ `selectSeries` The same as [`filter`](#filter) only the `iterator` is applied to each item in `arr` in -series. The next `iterator` is only called once the current one has completed. +series. The next `iterator` is only called once the current one has completed. The results array will be in the same order as the original. --------------------------------------- @@ -412,12 +414,12 @@ in series. __Aliases:__ `inject`, `foldl` Reduces `arr` into a single value using an async `iterator` to return -each successive step. `memo` is the initial state of the reduction. -This function only operates in series. +each successive step. `memo` is the initial state of the reduction. +This function only operates in series. -For performance reasons, it may make sense to split a call to this function into -a parallel map, and then use the normal `Array.prototype.reduce` on the results. -This function is for situations where each step in the reduction needs to be async; +For performance reasons, it may make sense to split a call to this function into +a parallel map, and then use the normal `Array.prototype.reduce` on the results. +This function is for situations where each step in the reduction needs to be async; if you can get the data before reducing it, then it's probably a good idea to do so. __Arguments__ @@ -426,9 +428,9 @@ __Arguments__ * `memo` - The initial state of the reduction. * `iterator(memo, item, callback)` - A function applied to each item in the array to produce the next step in the reduction. The `iterator` is passed a - `callback(err, reduction)` which accepts an optional error as its first - argument, and the state of the reduction as the second. If an error is - passed to the callback, the reduction is stopped and the main `callback` is + `callback(err, reduction)` which accepts an optional error as its first + argument, and the state of the reduction as the second. If an error is + passed to the callback, the reduction is stopped and the main `callback` is immediately called with the error. * `callback(err, result)` - A callback which is called after all the `iterator` functions have finished. Result is the reduced value. @@ -472,7 +474,7 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A truth test to apply to each item in `arr`. - The iterator is passed a `callback(truthValue)` which must be called with a + The iterator is passed a `callback(truthValue)` which must be called with a boolean argument once it has completed. * `callback(result)` - A callback which is called as soon as any iterator returns `true`, or after all the `iterator` functions have finished. Result will be @@ -564,7 +566,7 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A truth test to apply to each item in the array - in parallel. The iterator is passed a callback(truthValue) which must be + in parallel. The iterator is passed a callback(truthValue) which must be called with a boolean argument once it has completed. * `callback(result)` - A callback which is called as soon as any iterator returns `true`, or after all the iterator functions have finished. Result will be @@ -594,7 +596,7 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A truth test to apply to each item in the array - in parallel. The iterator is passed a callback(truthValue) which must be + in parallel. The iterator is passed a callback(truthValue) which must be called with a boolean argument once it has completed. * `callback(result)` - A callback which is called after all the `iterator` functions have finished. Result will be either `true` or `false` depending on @@ -622,7 +624,7 @@ __Arguments__ * `arr` - An array to iterate over. * `iterator(item, callback)` - A function to apply to each item in `arr`. - The iterator is passed a `callback(err, results)` which must be called once it + The iterator is passed a `callback(err, results)` which must be called once it has completed with an error (which can be `null`) and an array of results. * `callback(err, results)` - A callback which is called after all the `iterator` functions have finished, or an error occurs. Results is an array containing @@ -651,7 +653,7 @@ Same as [`concat`](#concat), but executes in series instead of parallel. Run the functions in the `tasks` array in series, each one running once the previous function has completed. If any functions in the series pass an error to its -callback, no more functions are run, and `callback` is immediately called with the value of the error. +callback, no more functions are run, and `callback` is immediately called with the value of the error. Otherwise, `callback` receives an array of results when `tasks` have completed. It is also possible to use an object instead of an array. Each property will be @@ -660,13 +662,13 @@ instead of an array. This can be a more readable way of handling results from [`series`](#series). **Note** that while many implementations preserve the order of object properties, the -[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) +[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) explicitly states that > The mechanics and order of enumerating the properties is not specified. So if you rely on the order in which your series of functions are executed, and want -this to work on all platforms, consider using an array. +this to work on all platforms, consider using an array. __Arguments__ @@ -674,7 +676,7 @@ __Arguments__ a `callback(err, result)` it must call on completion with an error `err` (which can be `null`) and an optional `result` value. * `callback(err, results)` - An optional callback to run once all the functions - have completed. This function gets a results array (or object) containing all + have completed. This function gets a results array (or object) containing all the result arguments passed to the `task` callbacks. __Example__ @@ -733,11 +735,11 @@ instead of an array. This can be a more readable way of handling results from __Arguments__ -* `tasks` - An array or object containing functions to run. Each function is passed - a `callback(err, result)` which it must call on completion with an error `err` +* `tasks` - An array or object containing functions to run. Each function is passed + a `callback(err, result)` which it must call on completion with an error `err` (which can be `null`) and an optional `result` value. * `callback(err, results)` - An optional callback to run once all the functions - have completed. This function gets a results array (or object) containing all + have completed. This function gets a results array (or object) containing all the result arguments passed to the task callbacks. __Example__ @@ -785,20 +787,20 @@ function(err, results) { ### parallelLimit(tasks, limit, [callback]) -The same as [`parallel`](#parallel), only `tasks` are executed in parallel +The same as [`parallel`](#parallel), only `tasks` are executed in parallel with a maximum of `limit` tasks executing at any time. -Note that the `tasks` are not executed in batches, so there is no guarantee that +Note that the `tasks` are not executed in batches, so there is no guarantee that the first `limit` tasks will complete before any others are started. __Arguments__ -* `tasks` - An array or object containing functions to run, each function is passed +* `tasks` - An array or object containing functions to run, each function is passed a `callback(err, result)` it must call on completion with an error `err` (which can be `null`) and an optional `result` value. * `limit` - The maximum number of `tasks` to run at any time. * `callback(err, results)` - An optional callback to run once all the functions - have completed. This function gets a results array (or object) containing all + have completed. This function gets a results array (or object) containing all the result arguments passed to the `task` callbacks. --------------------------------------- @@ -813,7 +815,7 @@ __Arguments__ * `test()` - synchronous truth test to perform before each execution of `fn`. * `fn(callback)` - A function which is called each time `test` passes. The function is - passed a `callback(err)`, which must be called once it has completed with an + passed a `callback(err)`, which must be called once it has completed with an optional `err` argument. * `callback(err)` - A callback which is called after the test fails and repeated execution of `fn` has stopped. @@ -840,8 +842,8 @@ async.whilst( ### doWhilst(fn, test, callback) -The post-check version of [`whilst`](#whilst). To reflect the difference in -the order of operations, the arguments `test` and `fn` are switched. +The post-check version of [`whilst`](#whilst). To reflect the difference in +the order of operations, the arguments `test` and `fn` are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. @@ -898,9 +900,9 @@ the error. __Arguments__ -* `tasks` - An array of functions to run, each function is passed a +* `tasks` - An array of functions to run, each function is passed a `callback(err, result1, result2, ...)` it must call on completion. The first - argument is an error (which can be `null`) and any further arguments will be + argument is an error (which can be `null`) and any further arguments will be passed as arguments in order to the next task. * `callback(err, [results])` - An optional callback to run once all the functions have completed. This will be passed the results of the last task's callback. @@ -923,7 +925,7 @@ async.waterfall([ callback(null, 'done'); } ], function (err, result) { - // result now equals 'done' + // result now equals 'done' }); ``` @@ -970,7 +972,7 @@ add1mul3(4, function (err, result) { ### seq(fn1, fn2...) Version of the compose function that is more natural to read. -Each following function consumes the return value of the latter function. +Each following function consumes the return value of the latter function. Each function is executed with the `this` binding of the composed function. @@ -984,7 +986,7 @@ __Example__ ```js // Requires lodash (or underscore), express3 and dresende's orm2. // Part of an app, that fetches cats of the logged user. -// This example uses `seq` function to avoid overnesting and error +// This example uses `seq` function to avoid overnesting and error // handling clutter. app.get('/cats', function(request, response) { function handleError(err, data, callback) { @@ -1016,7 +1018,7 @@ app.get('/cats', function(request, response) { ### applyEach(fns, args..., callback) -Applies the provided arguments to each function in the array, calling +Applies the provided arguments to each function in the array, calling `callback` after all functions have completed. If you only provide the first argument, then it will return a function which lets you pass in the arguments as if it were a single function call. @@ -1056,13 +1058,13 @@ The same as [`applyEach`](#applyEach) only the functions are applied in series. Creates a `queue` object with the specified `concurrency`. Tasks added to the `queue` are processed in parallel (up to the `concurrency` limit). If all -`worker`s are in progress, the task is queued until one becomes available. +`worker`s are in progress, the task is queued until one becomes available. Once a `worker` completes a `task`, that `task`'s callback is called. __Arguments__ * `worker(task, callback)` - An asynchronous function for processing a queued - task, which must call its `callback(err)` argument when finished, with an + task, which must call its `callback(err)` argument when finished, with an optional `error` as an argument. * `concurrency` - An `integer` for determining how many `worker` functions should be run in parallel. @@ -1079,11 +1081,11 @@ methods: * `concurrency` - an integer for determining how many `worker` functions should be run in parallel. This property can be changed after a `queue` is created to alter the concurrency on-the-fly. -* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once +* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once the `worker` has finished processing the task. Instead of a single task, a `tasks` array can be submitted. The respective callback is used for every task in the list. * `unshift(task, [callback])` - add a new task to the front of the `queue`. -* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, +* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, and further tasks will be queued. * `empty` - a callback that is called when the last item from the `queue` is given to a `worker`. * `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`. @@ -1148,7 +1150,7 @@ when the worker is finished. __Arguments__ * `worker(tasks, callback)` - An asynchronous function for processing an array of - queued tasks, which must call its `callback(err)` argument when finished, with + queued tasks, which must call its `callback(err)` argument when finished, with an optional `err` argument. * `payload` - An optional `integer` for determining how many tasks should be processed per round; if omitted, the default is unlimited. @@ -1163,7 +1165,7 @@ methods: process per round. This property can be changed after a `cargo` is created to alter the payload on-the-fly. * `push(task, [callback])` - Adds `task` to the `queue`. The callback is called - once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` + once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` can be submitted. The respective callback is used for every task in the list. * `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued. * `empty` - A callback that is called when the last item from the `queue` is given to a `worker`. @@ -1200,18 +1202,18 @@ cargo.push({name: 'baz'}, function (err) { ### auto(tasks, [callback]) -Determines the best order for running the functions in `tasks`, based on their -requirements. Each function can optionally depend on other functions being completed -first, and each function is run as soon as its requirements are satisfied. +Determines the best order for running the functions in `tasks`, based on their +requirements. Each function can optionally depend on other functions being completed +first, and each function is run as soon as its requirements are satisfied. -If any of the functions pass an error to their callback, it will not -complete (so any other functions depending on it will not run), and the main -`callback` is immediately called with the error. Functions also receive an +If any of the functions pass an error to their callback, it will not +complete (so any other functions depending on it will not run), and the main +`callback` is immediately called with the error. Functions also receive an object containing the results of functions which have completed so far. -Note, all functions are called with a `results` object as a second argument, +Note, all functions are called with a `results` object as a second argument, so it is unsafe to pass functions in the `tasks` object which cannot handle the -extra argument. +extra argument. For example, this snippet of code: @@ -1228,7 +1230,7 @@ argument, which will fail: fs.readFile('data.txt', 'utf-8', cb, {}); ``` -Instead, wrap the call to `readFile` in a function which does not forward the +Instead, wrap the call to `readFile` in a function which does not forward the `results` object: ```js @@ -1245,13 +1247,13 @@ __Arguments__ requirements, with the function itself the last item in the array. The object's key of a property serves as the name of the task defined by that property, i.e. can be used when specifying requirements for other tasks. - The function receives two arguments: (1) a `callback(err, result)` which must be - called when finished, passing an `error` (which can be `null`) and the result of + The function receives two arguments: (1) a `callback(err, result)` which must be + called when finished, passing an `error` (which can be `null`) and the result of the function's execution, and (2) a `results` object, containing the results of the previously executed functions. * `callback(err, results)` - An optional callback which is called when all the - tasks have been completed. It receives the `err` argument if any `tasks` - pass an error to their callback. Results are always returned; however, if + tasks have been completed. It receives the `err` argument if any `tasks` + pass an error to their callback. Results are always returned; however, if an error occurs, no further `tasks` will be performed, and the results object will only contain partial results. @@ -1342,7 +1344,7 @@ __Arguments__ * `times` - An integer indicating how many times to attempt the `task` before giving up. Defaults to 5. * `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)` - which must be called when finished, passing `err` (which can be `null`) and the `result` of + which must be called when finished, passing `err` (which can be `null`) and the `result` of the function's execution, and (2) a `results` object, containing the results of the previously executed functions (if nested inside another control flow). * `callback(err, results)` - An optional callback which is called when the @@ -1411,7 +1413,7 @@ node> nextfn(); ### apply(function, arguments..) -Creates a continuation function with some arguments already applied. +Creates a continuation function with some arguments already applied. Useful as a shorthand when combined with other control flow functions. Any arguments passed to the returned function are added to the arguments originally passed @@ -1518,7 +1520,7 @@ async.times(5, function(n, next){ ### timesSeries(n, callback) The same as [`times`](#times), only the iterator is applied to each item in `arr` in -series. The next `iterator` is only called once the current one has completed. +series. The next `iterator` is only called once the current one has completed. The results array will be in the same order as the original. @@ -1630,3 +1632,37 @@ node> async.dir(hello, 'world'); Changes the value of `async` back to its original value, returning a reference to the `async` object. + +--------------------------------------- + + +### stackSafe(function) + +Wrapper for iterators or other recursive functions that can potentially result in a +stack overflow if called recursively. Normally, the function argument is executed +immediately and synchronously. If `stackSafe` is called over a threshold number of +times in a single tick, the function argument is queued to be executed the next tick. +This helps to avoid stack overflows in long recursive chains. + +__Arguments__ + +* `function` - The function to execute either synchronously or in the next tick. + +__Example__ + +```js +async.stackSafe(function() { + // Do something synchronously or asynchronously +}); +``` + +--------------------------------------- + + +### wrapStackSafe(function) + +Wraps a given function in a `stackSafe` call. Returns the wrapped function. + +Note that there are stack safe versions of every async iterator call, in the form +`safeEachSeries` or `safeParallel`. These automatically wrap iterators in `stackSafe` +to prevent stack overflows. diff --git a/lib/async.js b/lib/async.js index 20afb3bd7..ab25ad29e 100755 --- a/lib/async.js +++ b/lib/async.js @@ -108,6 +108,41 @@ } } + var estimatedStackDepth = 0; + var stackDepthCallbackQueued = false; + function queueStackDepthCallback() { + if (!stackDepthCallbackQueued) { + stackDepthCallbackQueued = true; + async.nextTick(function() { + estimatedStackDepth = 0; + stackDepthCallbackQueued = false; + }); + } + } + + var maxEstimatedStackDepth = 128; + async.stackSafe = function (callback) { + if (!estimatedStackDepth) { + queueStackDepthCallback(); + } + if (estimatedStackDepth >= maxEstimatedStackDepth) { + async.nextTick(callback); + } else { + estimatedStackDepth++; + callback(); + } + }; + + async.wrapStackSafe = function (callback) { + return function() { + var args = Array.prototype.slice.call(arguments, 0); + var thisPtr = this; + async.stackSafe(function() { + callback.apply(thisPtr, args); + }); + }; + }; + async.each = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { @@ -1025,6 +1060,25 @@ next(); }; + // Create stack-safe variants of functions which can recurse and overflow the stack if the iterators aren't actually asynchronous + function makeStackSafeFunction(origFunc) { + return function() { + var newArgs = []; + for(var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] === 'function') { + newArgs.push(async.wrapStackSafe(arguments[i])); + } else { + newArgs.push(arguments[i]); + } + } + return origFunc.apply(this, newArgs); + }; + } + + for(var unsafeFuncName in async) { + async['safe' + unsafeFuncName.slice(0, 1).toUpperCase() + unsafeFuncName.slice(1)] = makeStackSafeFunction(async[unsafeFuncName]); + } + // Node.js if (typeof module !== 'undefined' && module.exports) { module.exports = async; diff --git a/test/test-async.js b/test/test-async.js index d9f2deee6..c9fe898df 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -618,7 +618,7 @@ exports['retry as an embedded task'] = function(test) { var retryResult = 'RETRY'; var fooResults; var retryResults; - + async.auto({ foo: function(callback, results){ fooResults = results; @@ -2563,20 +2563,20 @@ exports['cargo bulk task'] = function (test) { }; exports['cargo drain once'] = function (test) { - + var c = async.cargo(function (tasks, callback) { callback(); }, 3); - + var drainCounter = 0; c.drain = function () { drainCounter++; } - + for(var i = 0; i < 10; i++){ c.push(i); } - + setTimeout(function(){ test.equal(drainCounter, 1); test.done(); @@ -2584,17 +2584,17 @@ exports['cargo drain once'] = function (test) { }; exports['cargo drain twice'] = function (test) { - + var c = async.cargo(function (tasks, callback) { callback(); }, 3); - + var loadCargo = function(){ for(var i = 0; i < 10; i++){ c.push(i); } }; - + var drainCounter = 0; c.drain = function () { drainCounter++; @@ -2918,7 +2918,7 @@ exports['queue started'] = function(test) { var calls = []; var q = async.queue(function(task, cb) {}); - + test.equal(q.started, false); q.push([]); test.equal(q.started, true); @@ -2926,3 +2926,36 @@ exports['queue started'] = function(test) { }; +exports['stack safe initially synchronous'] = function(test) { + var flag = false; + async.stackSafe(function() { + test.equal(flag, false); + test.done(); + }); + flag = true; +}; + +exports['stack safe eventually asynchronous'] = function(test) { + for(var i = 0; i < 1000; i++) { + async.stackSafe(function() { }); + } + + var flag = false; + async.stackSafe(function() { + test.equal(flag, true); + test.done(); + }); + flag = true; +}; + +exports['stack safe eachSeries eventually asynchronous'] = function(test) { + var flag = false; + async.safeEachSeries(Array(1000), function(item, cb) { + cb(); + }, function() { + test.equal(flag, true); + test.done(); + }); + flag = true; +}; +