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;
+};
+