diff options
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | README.md | 875 | ||||
-rw-r--r-- | component.json | 11 | ||||
-rwxr-xr-x | lib/async.js | 107 | ||||
-rw-r--r-- | package.json | 8 | ||||
-rwxr-xr-x | test/test-async.js | 337 |
6 files changed, 913 insertions, 427 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2010 Caolan McMahon +Copyright (c) 2010-2014 Caolan McMahon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -5,14 +5,14 @@ Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed for -use with [node.js](http://nodejs.org), it can also be used directly in the -browser. +use with [Node.js](http://nodejs.org), it can also be used directly in the +browser. Also supports [component](https://github.com/component/component). Async provides around 20 functions that include the usual 'functional' -suspects (map, reduce, filter, each…) as well as some common patterns -for asynchronous control flow (parallel, series, waterfall…). All these -functions assume you follow the node.js convention of providing a single -callback as the last argument of your async function. +suspects (`map`, `reduce`, `filter`, `each`…) as well as some common patterns +for asynchronous control flow (`parallel`, `series`, `waterfall`…). All these +functions assume you follow the Node.js convention of providing a single +callback as the last argument of your `async` function. ## Quick Examples @@ -22,7 +22,7 @@ async.map(['file1','file2','file3'], fs.stat, function(err, results){ // results is now an array of stats for each file }); -async.filter(['file1','file2','file3'], path.exists, function(results){ +async.filter(['file1','file2','file3'], fs.exists, function(results){ // results now equals an array of the existing files }); @@ -45,8 +45,8 @@ missing please create a GitHub issue for it. ### Binding a context to an iterator -This section is really about bind, not about async. If you are wondering how to -make async execute your iterators in a given context, or are confused as to why +This section is really about `bind`, not about `async`. If you are wondering how to +make `async` execute your iterators in a given context, or are confused as to why a method of another library isn't working as an iterator, study this example: ```js @@ -81,7 +81,7 @@ async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), fun The source is available for download from [GitHub](http://github.com/caolan/async). -Alternatively, you can install using Node Package Manager (npm): +Alternatively, you can install using Node Package Manager (`npm`): npm install async @@ -89,7 +89,9 @@ __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. Usage: +So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. + +Usage: ```html <script type="text/javascript" src="async.js"></script> @@ -106,45 +108,57 @@ So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: ### Collections -* [each](#each) -* [map](#map) -* [filter](#filter) -* [reject](#reject) -* [reduce](#reduce) -* [detect](#detect) -* [sortBy](#sortBy) -* [some](#some) -* [every](#every) -* [concat](#concat) +* [`each`](#each) +* [`eachSeries`](#eachSeries) +* [`eachLimit`](#eachLimit) +* [`map`](#map) +* [`mapSeries`](#mapSeries) +* [`mapLimit`](#mapLimit) +* [`filter`](#filter) +* [`filterSeries`](#filterSeries) +* [`reject`](#reject) +* [`rejectSeries`](#rejectSeries) +* [`reduce`](#reduce) +* [`reduceRight`](#reduceRight) +* [`detect`](#detect) +* [`detectSeries`](#detectSeries) +* [`sortBy`](#sortBy) +* [`some`](#some) +* [`every`](#every) +* [`concat`](#concat) +* [`concatSeries`](#concatSeries) ### Control Flow -* [series](#series) -* [parallel](#parallel) -* [whilst](#whilst) -* [doWhilst](#doWhilst) -* [until](#until) -* [doUntil](#doUntil) -* [forever](#forever) -* [waterfall](#waterfall) -* [compose](#compose) -* [applyEach](#applyEach) -* [queue](#queue) -* [cargo](#cargo) -* [auto](#auto) -* [iterator](#iterator) -* [apply](#apply) -* [nextTick](#nextTick) -* [times](#times) -* [timesSeries](#timesSeries) +* [`series`](#series) +* [`parallel`](#parallel) +* [`parallelLimit`](#parallellimittasks-limit-callback) +* [`whilst`](#whilst) +* [`doWhilst`](#doWhilst) +* [`until`](#until) +* [`doUntil`](#doUntil) +* [`forever`](#forever) +* [`waterfall`](#waterfall) +* [`compose`](#compose) +* [`seq`](#seq) +* [`applyEach`](#applyEach) +* [`applyEachSeries`](#applyEachSeries) +* [`queue`](#queue) +* [`cargo`](#cargo) +* [`auto`](#auto) +* [`iterator`](#iterator) +* [`apply`](#apply) +* [`nextTick`](#nextTick) +* [`times`](#times) +* [`timesSeries`](#timesSeries) ### Utils -* [memoize](#memoize) -* [unmemoize](#unmemoize) -* [log](#log) -* [dir](#dir) -* [noConflict](#noConflict) +* [`memoize`](#memoize) +* [`unmemoize`](#unmemoize) +* [`log`](#log) +* [`dir`](#dir) +* [`noConflict`](#noConflict) ## Collections @@ -153,25 +167,26 @@ So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: <a name="each" /> ### each(arr, iterator, callback) -Applies an iterator function to each item in an array, in parallel. -The iterator is called with an item from the list and a callback for when it -has finished. If the iterator passes an error to this callback, the main -callback for the each function is immediately called with the error. +Applies the function `iterator` to each item in `arr`, in parallel. +The `iterator` is called with an item from the list, and a callback for when it +has finished. If the `iterator` passes an error to its `callback`, the main +`callback` (for the `each` function) is immediately called with the error. -Note, that since this function applies the iterator to each item in parallel +Note, that since this function applies `iterator` to each item in parallel, there is no guarantee that the iterator functions will complete in order. __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A function to apply to each item in the array. - 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 after all the iterator functions - have finished, or an error has occurred. +* `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 + arguments or with an explicit `null` argument. +* `callback(err)` - A callback which is called when all `iterator` functions + have finished, or an error occurs. + +__Examples__ -__Example__ ```js // assuming openFiles is an array of file names and saveFile is a function @@ -182,15 +197,46 @@ async.each(openFiles, saveFile, function(err){ }); ``` +```js +// assuming openFiles is an array of file names and saveFile is a function +// to save the modified contents of that file: + +async.each(openFiles, function( file, callback) { + + // Perform operation on file here. + console.log('Processing file ' + file); + callback(); + + if( file.length > 32 ) { + console.log('This file name is too long'); + callback('File name too long'); + + return; + } else { + console.log('File saved'); + callback(); + } +}, function(err){ + // if any of the saves produced an error, err would equal that error + if( err ) { + // One of the iterations produced an error. + // All processing will now stop. + console.log('A file failed to process'); + } else { + console.log('All files have been processed successfully'); + } +}); +``` + --------------------------------------- <a name="forEachSeries" /> <a name="eachSeries" /> ### eachSeries(arr, iterator, callback) -The same as each only the iterator is applied to each item in the array in -series. The next iterator is only called once the current one has completed -processing. This means the iterator functions will complete in order. +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. +This means the `iterator` functions will complete in order. --------------------------------------- @@ -199,23 +245,22 @@ processing. This means the iterator functions will complete in order. <a name="eachLimit" /> ### eachLimit(arr, limit, iterator, callback) -The same as each only no more than "limit" iterators 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 are not processed in batches, so there is no guarantee that - the first "limit" iterator functions will complete before any others are -started. +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__ -* arr - An array to iterate over. -* limit - The maximum number of iterators to run at any time. -* iterator(item, callback) - A function to apply to each item in the array. - The iterator is passed a callback(err) which must be called once it has +* `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 - arguments or with an explicit null argument. -* callback(err) - A callback which is called after all the iterator functions - have finished, or an error has occurred. + arguments or with an explicit `null` argument. +* `callback(err)` - A callback which is called when all `iterator` functions + have finished, or an error occurs. __Example__ @@ -233,26 +278,25 @@ async.eachLimit(documents, 20, requestApi, function(err){ <a name="map" /> ### map(arr, iterator, callback) -Produces a new array of values by mapping each value in the given array through -the iterator function. The iterator is called with an item from the array and a -callback for when it has finished processing. The callback takes 2 arguments, -an error and the transformed item from the array. If the iterator passes an -error to this callback, the main callback for the map function is immediately -called with the error. +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, 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, however -the results array will be in the same order as the original array. +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. +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 the array. - 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 after all the iterator - functions have finished, or an error has occurred. Results is an array of the - transformed items from the original array. +* `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 + 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 + transformed items from the `arr`. __Example__ @@ -267,9 +311,9 @@ async.map(['file1','file2','file3'], fs.stat, function(err, results){ <a name="mapSeries" /> ### mapSeries(arr, iterator, callback) -The same as map only the iterator is applied to each item in the array in -series. The next iterator is only called once the current one has completed -processing. The results array will be in the same order as the original. +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. +The results array will be in the same order as the original. --------------------------------------- @@ -277,87 +321,88 @@ processing. The results array will be in the same order as the original. <a name="mapLimit" /> ### mapLimit(arr, limit, iterator, callback) -The same as map only no more than "limit" iterators 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 - the first "limit" iterator functions will complete before any others are -started. +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__ -* arr - An array to iterate over. -* limit - The maximum number of iterators to run at any time. -* iterator(item, callback) - A function to apply to each item in the array. - 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 after all the iterator - functions have finished, or an error has occurred. Results is an array of the - transformed items from the original array. +* `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 + 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 + transformed items from the original `arr`. __Example__ ```js -async.map(['file1','file2','file3'], 1, fs.stat, function(err, results){ +async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){ // results is now an array of stats for each file }); ``` --------------------------------------- +<a name="select" /> <a name="filter" /> ### filter(arr, iterator, callback) -__Alias:__ select +__Alias:__ `select` -Returns a new array of all the values which pass an async truth test. -_The callback for each iterator call only accepts a single argument of true or -false, it does not accept an error argument first!_ This is in-line with the -way node libraries work with truth tests like path.exists. This operation is +Returns a new array of all the values in `arr` which pass an async truth test. +_The callback for each `iterator` call only accepts a single argument of `true` or +`false`; it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like `fs.exists`. This operation is performed in parallel, but the results array will be in the same order as the original. __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A truth test to apply to each item in the array. - The iterator is passed a callback(truthValue) which must be called with a +* `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 boolean argument once it has completed. -* callback(results) - A callback which is called after all the iterator +* `callback(results)` - A callback which is called after all the `iterator` functions have finished. __Example__ ```js -async.filter(['file1','file2','file3'], path.exists, function(results){ +async.filter(['file1','file2','file3'], fs.exists, function(results){ // results now equals an array of the existing files }); ``` --------------------------------------- +<a name="selectSeries" /> <a name="filterSeries" /> ### filterSeries(arr, iterator, callback) -__alias:__ selectSeries +__Alias:__ `selectSeries` -The same as filter only the iterator is applied to each item in the array in -series. The next iterator is only called once the current one has completed -processing. The results array will be in the same order as the original. +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. +The results array will be in the same order as the original. --------------------------------------- <a name="reject" /> ### reject(arr, iterator, callback) -The opposite of filter. Removes values that pass an async truth test. +The opposite of [`filter`](#filter). Removes values that pass an `async` truth test. --------------------------------------- <a name="rejectSeries" /> ### rejectSeries(arr, iterator, callback) -The same as reject, only the iterator is applied to each item in the array +The same as [`reject`](#reject), only the `iterator` is applied to each item in `arr` in series. @@ -366,27 +411,28 @@ in series. <a name="reduce" /> ### reduce(arr, memo, iterator, callback) -__aliases:__ inject, foldl +__Aliases:__ `inject`, `foldl` -Reduces a list of values 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. For performance reasons, it may make sense to -split a call to this function into a parallel map, 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. +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. + +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__ -* arr - An array to iterate over. -* 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 +* `arr` - An array to iterate over. +* `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 + 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 +* `callback(err, result)` - A callback which is called after all the `iterator` functions have finished. Result is the reduced value. __Example__ @@ -407,9 +453,9 @@ async.reduce([1,2,3], 0, function(memo, item, callback){ <a name="reduceRight" /> ### reduceRight(arr, memo, iterator, callback) -__Alias:__ foldr +__Alias:__ `foldr` -Same as reduce, only operates on the items in the array in reverse order. +Same as [`reduce`](#reduce), only operates on `arr` in reverse order. --------------------------------------- @@ -417,28 +463,28 @@ Same as reduce, only operates on the items in the array in reverse order. <a name="detect" /> ### detect(arr, iterator, callback) -Returns the first value in a list that passes an async truth test. The -iterator is applied in parallel, meaning the first iterator to return true will -fire the detect callback with that result. That means the result might not be -the first item in the original array (in terms of order) that passes the test. +Returns the first value in `arr` that passes an async truth test. The +`iterator` is applied in parallel, meaning the first iterator to return `true` will +fire the detect `callback` with that result. That means the result might not be +the first item in the original `arr` (in terms of order) that passes the test. -If order within the original array is important then look at detectSeries. +If order within the original `arr` is important, then look at [`detectSeries`](#detectSeries). __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A truth test to apply to each item in the array. - The iterator is passed a callback(truthValue) which must be called with a +* `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 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 +* `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 the first item in the array that passes the truth test (iterator) or the - value undefined if none passed. + value `undefined` if none passed. __Example__ ```js -async.detect(['file1','file2','file3'], path.exists, function(result){ +async.detect(['file1','file2','file3'], fs.exists, function(result){ // result now equals the first file in the list that exists }); ``` @@ -448,8 +494,8 @@ async.detect(['file1','file2','file3'], path.exists, function(result){ <a name="detectSeries" /> ### detectSeries(arr, iterator, callback) -The same as detect, only the iterator is applied to each item in the array -in series. This means the result is always the first in the original array (in +The same as [`detect`](#detect), only the `iterator` is applied to each item in `arr` +in series. This means the result is always the first in the original `arr` (in terms of array order) that passes the truth test. @@ -458,18 +504,18 @@ terms of array order) that passes the truth test. <a name="sortBy" /> ### sortBy(arr, iterator, callback) -Sorts a list by the results of running each value through an async iterator. +Sorts a list by the results of running each `arr` value through an async `iterator`. __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A function to apply to each item in the array. - The iterator is passed a callback(err, sortValue) which must be called once it - has completed with an error (which can be null) and a value to use as the sort +* `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, sortValue)` which must be called once it + has completed with an error (which can be `null`) and a value to use as the sort criteria. -* callback(err, results) - A callback which is called after all the iterator - functions have finished, or an error has occurred. Results is the items from - the original array sorted by the values returned by the iterator calls. +* `callback(err, results)` - A callback which is called after all the `iterator` + functions have finished, or an error occurs. Results is the items from + the original `arr` sorted by the values returned by the `iterator` calls. __Example__ @@ -489,28 +535,28 @@ async.sortBy(['file1','file2','file3'], function(file, callback){ <a name="some" /> ### some(arr, iterator, callback) -__Alias:__ any +__Alias:__ `any` -Returns true if at least one element in the array satisfies an async test. -_The callback for each iterator call only accepts a single argument of true or -false, it does not accept an error argument first!_ This is in-line with the -way node libraries work with truth tests like path.exists. Once any iterator -call returns true, the main callback is immediately called. +Returns `true` if at least one element in the `arr` satisfies an async test. +_The callback for each iterator call only accepts a single argument of `true` or +`false`; it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like `fs.exists`. Once any iterator +call returns `true`, the main `callback` is immediately called. __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A truth test to apply to each item in the array. - 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 - either true or false depending on the values of the async tests. +* `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 + 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 + either `true` or `false` depending on the values of the async tests. __Example__ ```js -async.some(['file1','file2','file3'], path.exists, function(result){ +async.some(['file1','file2','file3'], fs.exists, function(result){ // if result is true then at least one of the files exists }); ``` @@ -520,27 +566,27 @@ async.some(['file1','file2','file3'], path.exists, function(result){ <a name="every" /> ### every(arr, iterator, callback) -__Alias:__ all +__Alias:__ `all` -Returns true if every element in the array satisfies an async test. -_The callback for each iterator call only accepts a single argument of true or -false, it does not accept an error argument first!_ This is in-line with the -way node libraries work with truth tests like path.exists. +Returns `true` if every element in `arr` satisfies an async test. +_The callback for each `iterator` call only accepts a single argument of `true` or +`false`; it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like `fs.exists`. __Arguments__ -* arr - An array to iterate over. -* iterator(item, callback) - A truth test to apply to each item in the array. - 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 +* `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 + 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 the values of the async tests. __Example__ ```js -async.every(['file1','file2','file3'], path.exists, function(result){ +async.every(['file1','file2','file3'], fs.exists, function(result){ // if result is true then every file exists }); ``` @@ -550,20 +596,20 @@ async.every(['file1','file2','file3'], path.exists, function(result){ <a name="concat" /> ### concat(arr, iterator, callback) -Applies an iterator to each item in a list, concatenating the results. Returns the -concatenated list. The iterators are called in parallel, and the results are +Applies `iterator` to each item in `arr`, concatenating the results. Returns the +concatenated list. The `iterator`s are called in parallel, and the results are concatenated as they return. There is no guarantee that the results array will -be returned in the original order of the arguments passed to the iterator function. +be returned in the original order of `arr` passed to the `iterator` function. __Arguments__ -* arr - An array to iterate over -* iterator(item, callback) - A function to apply to each item in the array. - 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 has occurred. Results is an array containing - the concatenated results of the iterator function. +* `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 + 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 + the concatenated results of the `iterator` function. __Example__ @@ -578,7 +624,7 @@ async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){ <a name="concatSeries" /> ### concatSeries(arr, iterator, callback) -Same as async.concat, but executes in series instead of parallel. +Same as [`concat`](#concat), but executes in series instead of parallel. ## Control Flow @@ -586,26 +632,33 @@ Same as async.concat, but executes in series instead of parallel. <a name="series" /> ### series(tasks, [callback]) -Run an array of functions in series, each one running once the previous +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 the callback for the series is -immediately called with the value of the error. Once the tasks have completed, -the results are passed to the final callback as an array. +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 -run as a function and the results will be passed to the final callback as an object +run as a function, and the results will be passed to the final `callback` as an object instead of an array. This can be a more readable way of handling results from -async.series. +[`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) +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. __Arguments__ -* 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 (which can - be null) and an optional result value. -* callback(err, results) - An optional callback to run once all the functions +* `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. +* `callback(err, results)` - An optional callback to run once all the functions have completed. This function gets a results array (or object) containing all - the result arguments passed to the task callbacks. + the result arguments passed to the `task` callbacks. __Example__ @@ -649,24 +702,24 @@ function(err, results) { <a name="parallel" /> ### parallel(tasks, [callback]) -Run an array of functions in parallel, without waiting until the previous +Run the `tasks` array of functions in parallel, without waiting until the previous function has completed. If any of the functions pass an error to its -callback, the main callback is immediately called with the value of the error. -Once the tasks have completed, the results are passed to the final callback as an +callback, the main `callback` is immediately called with the value of the error. +Once the `tasks` have completed, the results are passed to the final `callback` as an array. It is also possible to use an object instead of an array. Each property will be -run as a function and the results will be passed to the final callback as an object +run as a function and the results will be passed to the final `callback` as an object instead of an array. This can be a more readable way of handling results from -async.parallel. +[`parallel`](#parallel). __Arguments__ -* 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 (which can - be null) and an optional result value. -* callback(err, results) - An optional callback to run once all the functions +* `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 the result arguments passed to the task callbacks. @@ -712,41 +765,41 @@ function(err, results) { --------------------------------------- -<a name="parallel" /> +<a name="parallelLimit" /> ### parallelLimit(tasks, limit, [callback]) -The same as parallel only the tasks are executed in parallel with a maximum of "limit" -tasks executing at any time. +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 -the first "limit" tasks will complete before any others are started. +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 - a callback(err, result) it must call on completion with an error (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 +* `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 - the result arguments passed to the task callbacks. + the result arguments passed to the `task` callbacks. --------------------------------------- <a name="whilst" /> ### whilst(test, fn, callback) -Repeatedly call fn, while test returns true. Calls the callback when stopped, +Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped, or an error occurs. __Arguments__ -* test() - synchronous truth test to perform before each execution of fn. -* fn(callback) - A function to call each time the test passes. The function is - passed a callback(err) which must be called once it has completed with an - optional error argument. -* callback(err) - A callback which is called after the test fails and repeated - execution of fn has stopped. +* `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 + optional `err` argument. +* `callback(err)` - A callback which is called after the test fails and repeated + execution of `fn` has stopped. __Example__ @@ -770,51 +823,69 @@ async.whilst( <a name="doWhilst" /> ### doWhilst(fn, test, callback) -The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. +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. --------------------------------------- <a name="until" /> ### until(test, fn, callback) -Repeatedly call fn, until test returns true. Calls the callback when stopped, +Repeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped, or an error occurs. -The inverse of async.whilst. +The inverse of [`whilst`](#whilst). --------------------------------------- <a name="doUntil" /> ### doUntil(fn, test, callback) -Like doWhilst except the test is inverted. Note the argument ordering differs from `until`. +Like [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`. --------------------------------------- <a name="forever" /> -### forever(fn, callback) +### forever(fn, errback) + +Calls the asynchronous function `fn` with a callback parameter that allows it to +call itself again, in series, indefinitely. -Calls the asynchronous function 'fn' repeatedly, in series, indefinitely. -If an error is passed to fn's callback then 'callback' is called with the -error, otherwise it will never be called. +If an error is passed to the callback then `errback` is called with the +error, and execution stops, otherwise it will never be called. + +```js +async.forever( + function(next) { + // next is suitable for passing to things that need a callback(err [, whatever]); + // it will result in this function being called again. + }, + function(err) { + // if next is called with a value in its first parameter, it will appear + // in here as 'err', and execution will stop. + } +); +``` --------------------------------------- <a name="waterfall" /> ### waterfall(tasks, [callback]) -Runs an array of functions in series, each passing their results to the next in -the array. However, if any of the functions pass an error to the callback, the -next function is not executed and the main callback is immediately called with +Runs the `tasks` array of functions in series, each passing their results to the next in +the array. However, if any of the `tasks` pass an error to their own callback, the +next function is not executed, and the main `callback` is immediately called with the error. __Arguments__ -* 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 +* `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 passed as arguments in order to the next task. -* callback(err, [results]) - An optional callback to run once all the functions +* `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. @@ -827,6 +898,7 @@ async.waterfall([ callback(null, 'one', 'two'); }, function(arg1, arg2, callback){ + // arg1 now equals 'one' and arg2 now equals 'two' callback(null, 'three'); }, function(arg1, callback){ @@ -844,14 +916,14 @@ async.waterfall([ Creates a function which is a composition of the passed asynchronous functions. Each function consumes the return value of the function that -follows. Composing functions f(), g() and h() would produce the result of -f(g(h())), only this version uses callbacks to obtain the return values. +follows. Composing functions `f()`, `g()`, and `h()` would produce the result of +`f(g(h()))`, only this version uses callbacks to obtain the return values. Each function is executed with the `this` binding of the composed function. __Arguments__ -* functions... - the asynchronous functions to compose +* `functions...` - the asynchronous functions to compose __Example__ @@ -877,19 +949,66 @@ add1mul3(4, function (err, result) { ``` --------------------------------------- +<a name="seq" /> +### 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 function is executed with the `this` binding of the composed function. + +__Arguments__ + +* functions... - the asynchronous functions to compose + + +__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 +// handling clutter. +app.get('/cats', function(request, response) { + function handleError(err, data, callback) { + if (err) { + console.error(err); + response.json({ status: 'error', message: err.message }); + } + else { + callback(data); + } + } + var User = request.models.User; + asyc.seq( + _.bind(User.get, User), // 'User.get' has signature (id, callback(err, data)) + handleError, + function(user, fn) { + user.getCats(fn); // 'getCats' has signature (callback(err, data)) + }, + handleError, + function(cats) { + response.json({ status: 'ok', message: 'Cats found', data: cats }); + } + )(req.session.user_id); + } +}); +``` + +--------------------------------------- <a name="applyEach" /> ### applyEach(fns, args..., callback) -Applies the provided arguments to each function in the array, calling the -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 +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. __Arguments__ -* fns - the asynchronous functions to all call with the same arguments -* args... - any number of separate arguments to pass to the function -* callback - the final argument should be the callback, called when all +* `fns` - the asynchronous functions to all call with the same arguments +* `args...` - any number of separate arguments to pass to the function +* `callback` - the final argument should be the callback, called when all functions have completed processing @@ -911,42 +1030,43 @@ async.each( <a name="applyEachSeries" /> ### applyEachSeries(arr, iterator, callback) -The same as applyEach only the functions are applied in series. +The same as [`applyEach`](#applyEach) only the functions are applied in series. --------------------------------------- <a name="queue" /> ### queue(worker, concurrency) -Creates a queue object with the specified concurrency. Tasks added to the -queue will be processed in parallel (up to the concurrency limit). If all -workers are in progress, the task is queued until one is available. Once -a worker has completed a task, the task's callback is called. +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. +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 - optional error as an argument. -* concurrency - An integer for determining how many worker functions should be +* `worker(task, callback)` - An asynchronous function for processing a queued + 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. __Queue objects__ -The queue object returned by this function has the following properties and +The `queue` object returned by this function has the following properties and methods: -* length() - a function returning the number of items waiting to be processed. -* 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 +* `length()` - a function returning the number of items waiting to be processed. +* `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, the callback is called - 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. -* 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 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 +* `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, + 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`. __Example__ @@ -991,34 +1111,38 @@ q.unshift({name: 'bar'}, function (err) { <a name="cargo" /> ### cargo(worker, [payload]) -Creates a cargo object with the specified payload. Tasks added to the -cargo will be processed altogether (up to the payload limit). If the -worker is in progress, the task is queued until it is available. Once -the worker has completed some tasks, each callback of those tasks is called. +Creates a `cargo` object with the specified payload. Tasks added to the +cargo will be processed altogether (up to the `payload` limit). If the +`worker` is in progress, the task is queued until it becomes available. Once +the `worker` has completed some tasks, each callback of those tasks is called. + +While [queue](#queue) passes only one task to one of a group of workers +at a time, cargo passes an array of tasks to a single worker, repeating +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 - an optional error as an argument. -* payload - An optional integer for determining how many tasks should be +* `worker(tasks, callback)` - An asynchronous function for processing an array of + 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. __Cargo objects__ -The cargo object returned by this function has the following properties and +The `cargo` object returned by this function has the following properties and methods: -* length() - a function returning the number of items waiting to be processed. -* payload - an integer for determining how many tasks should be - process per round. This property can be changed after a cargo is created to +* `length()` - A function returning the number of items waiting to be processed. +* `payload` - An `integer` for determining how many tasks should be + process per round. This property can be changed after a `cargo` is created to alter the payload on-the-fly. -* push(task, [callback]) - add a new 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 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 -* drain - a callback that is called when the last item from the queue has returned from the worker +* `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` + 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`. +* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`. __Example__ @@ -1051,33 +1175,36 @@ cargo.push({name: 'baz'}, function (err) { <a name="auto" /> ### auto(tasks, [callback]) -Determines the best order for running functions 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, that function will not complete -(so any other functions depending on it will not run) and the main callback -will be called immediately with the error. Functions also receive an object -containing the results of functions which have completed so far. +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 +object containing the results of functions which have completed so far. + +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. -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. For example, this snippet of code: +For example, this snippet of code: ```js async.auto({ - readData: async.apply(fs.readFile, 'data.txt', 'utf-8'); + readData: async.apply(fs.readFile, 'data.txt', 'utf-8') }, callback); ``` -will have the effect of calling readFile with the results object as the last +will have the effect of calling `readFile` with the results object as the last argument, which will fail: ```js fs.readFile('data.txt', 'utf-8', cb, {}); ``` -Instead, wrap the call to readFile in a function which does not forward the -results object: +Instead, wrap the call to `readFile` in a function which does not forward the +`results` object: ```js async.auto({ @@ -1089,40 +1216,50 @@ async.auto({ __Arguments__ -* tasks - An object literal containing named functions or an array of - requirements, with the function itself the last item in the array. The key +* `tasks` - An object literal (containing named functions) or an array (of + requirements, with the function itself the last item in the array). The key used for each function or array is used when specifying requirements. 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 + 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. The callback will receive an error as an argument - if any tasks pass an error to their callback. Results will always be passed - but if an error occurred, no other tasks will be performed, and the results - object will only contain partial results. - +* `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 + an error occurs, no further `tasks` will be performed, and the results + object will only contain partial results. + __Example__ ```js async.auto({ get_data: function(callback){ + console.log('in get_data'); // async code to get some data + callback(null, 'data', 'converted to array'); }, make_folder: function(callback){ + console.log('in make_folder'); // async code to create a directory to store a file in // this is run at the same time as getting the data + callback(null, 'folder'); }, - write_file: ['get_data', 'make_folder', function(callback){ + write_file: ['get_data', 'make_folder', function(callback, results){ + console.log('in write_file', JSON.stringify(results)); // once there is some data and the directory exists, // write the data to a file in the directory - callback(null, filename); + callback(null, 'filename'); }], email_link: ['write_file', function(callback, results){ + console.log('in email_link', JSON.stringify(results)); // once the file is written let's email a link to it... // results.write_file contains the filename returned by write_file. + callback(null, {'file':results.write_file, 'email':'user@example.com'}); }] +}, function(err, results) { + console.log('err = ', err); + console.log('results = ', results); }); ``` @@ -1132,28 +1269,37 @@ series functions would look like this: ```js async.parallel([ function(callback){ + console.log('in get_data'); // async code to get some data + callback(null, 'data', 'converted to array'); }, function(callback){ + console.log('in make_folder'); // async code to create a directory to store a file in // this is run at the same time as getting the data + callback(null, 'folder'); } ], function(err, results){ async.series([ function(callback){ + console.log('in write_file', JSON.stringify(results)); // once there is some data and the directory exists, // write the data to a file in the directory + results.push('filename'); + callback(null); }, function(callback){ + console.log('in email_link', JSON.stringify(results)); // once the file is written let's email a link to it... + callback(null, {'file':results.pop(), 'email':'user@example.com'}); } ]); }); ``` -For a complicated series of async tasks using the auto function makes adding -new tasks much easier and makes the code more readable. +For a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding +new tasks much easier (and the code more readable). --------------------------------------- @@ -1161,16 +1307,16 @@ new tasks much easier and makes the code more readable. <a name="iterator" /> ### iterator(tasks) -Creates an iterator function which calls the next function in the array, +Creates an iterator function which calls the next function in the `tasks` array, returning a continuation to call the next one after that. It's also possible to -'peek' the next iterator by doing iterator.next(). +“peek” at the next iterator with `iterator.next()`. -This function is used internally by the async module but can be useful when +This function is used internally by the `async` module, but can be useful when you want to manually control the flow of functions in series. __Arguments__ -* tasks - An array of functions to run. +* `tasks` - An array of functions to run. __Example__ @@ -1197,15 +1343,16 @@ node> nextfn(); <a name="apply" /> ### apply(function, arguments..) -Creates a continuation function with some arguments already applied, a useful -shorthand when combined with other control flow functions. Any arguments +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 to apply. __Arguments__ -* function - The function you want to eventually apply all arguments to. -* arguments... - Any number of arguments to automatically apply when the +* `function` - The function you want to eventually apply all arguments to. +* `arguments...` - Any number of arguments to automatically apply when the continuation is called. __Example__ @@ -1247,16 +1394,16 @@ three <a name="nextTick" /> ### nextTick(callback) -Calls the callback on a later loop around the event loop. In node.js this just -calls process.nextTick, in the browser it falls back to setImmediate(callback) -if available, otherwise setTimeout(callback, 0), which means other higher priority -events may precede the execution of the callback. +Calls `callback` on a later loop around the event loop. In Node.js this just +calls `process.nextTick`; in the browser it falls back to `setImmediate(callback)` +if available, otherwise `setTimeout(callback, 0)`, which means other higher priority +events may precede the execution of `callback`. This is used internally for browser-compatibility purposes. __Arguments__ -* callback - The function to call on a later loop around the event loop. +* `callback` - The function to call on a later loop around the event loop. __Example__ @@ -1272,13 +1419,13 @@ call_order.push('one') <a name="times" /> ### times(n, callback) -Calls the callback n times and accumulates results in the same manner -you would use with async.map. +Calls the `callback` function `n` times, and accumulates results in the same manner +you would use with [`map`](#map). __Arguments__ -* n - The number of times to run the function. -* callback - The function to call n times. +* `n` - The number of times to run the function. +* `callback` - The function to call `n` times. __Example__ @@ -1302,9 +1449,9 @@ async.times(5, function(n, next){ <a name="timesSeries" /> ### timesSeries(n, callback) -The same as times only the iterator is applied to each item in the array in -series. The next iterator is only called once the current one has completed -processing. The results array will be in the same order as the original. +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. +The results array will be in the same order as the original. ## Utils @@ -1312,7 +1459,7 @@ processing. The results array will be in the same order as the original. <a name="memoize" /> ### memoize(fn, [hasher]) -Caches the results of an async function. When creating a hash to store function +Caches the results of an `async` function. When creating a hash to store function results against, the callback is omitted from the hash and an optional hash function can be used. @@ -1321,9 +1468,9 @@ by `memoize`. __Arguments__ -* fn - the function you to proxy and cache results from. -* hasher - an optional function for generating a custom hash for storing - results, it has all the arguments applied to it apart from the callback, and +* `fn` - The function to proxy and cache results from. +* `hasher` - Tn optional function for generating a custom hash for storing + results. It has all the arguments applied to it apart from the callback, and must be synchronous. __Example__ @@ -1344,25 +1491,25 @@ fn('some name', function () { <a name="unmemoize" /> ### unmemoize(fn) -Undoes a memoized function, reverting it to the original, unmemoized -form. Comes handy in tests. +Undoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized +form. Handy for testing. __Arguments__ -* fn - the memoized function +* `fn` - the memoized function <a name="log" /> ### log(function, arguments) -Logs the result of an async function to the console. Only works in node.js or -in browsers that support console.log and console.error (such as FF and Chrome). -If multiple arguments are returned from the async function, console.log is +Logs the result of an `async` function to the `console`. Only works in Node.js or +in browsers that support `console.log` and `console.error` (such as FF and Chrome). +If multiple arguments are returned from the async function, `console.log` is called on each argument in order. __Arguments__ -* function - The function you want to eventually apply all arguments to. -* arguments... - Any number of arguments to apply to the function. +* `function` - The function you want to eventually apply all arguments to. +* `arguments...` - Any number of arguments to apply to the function. __Example__ @@ -1383,16 +1530,16 @@ node> async.log(hello, 'world'); <a name="dir" /> ### dir(function, arguments) -Logs the result of an async function to the console using console.dir to -display the properties of the resulting object. Only works in node.js or -in browsers that support console.dir and console.error (such as FF and Chrome). -If multiple arguments are returned from the async function, console.dir is +Logs the result of an `async` function to the `console` using `console.dir` to +display the properties of the resulting object. Only works in Node.js or +in browsers that support `console.dir` and `console.error` (such as FF and Chrome). +If multiple arguments are returned from the async function, `console.dir` is called on each argument in order. __Arguments__ -* function - The function you want to eventually apply all arguments to. -* arguments... - Any number of arguments to apply to the function. +* `function` - The function you want to eventually apply all arguments to. +* `arguments...` - Any number of arguments to apply to the function. __Example__ @@ -1413,5 +1560,5 @@ node> async.dir(hello, 'world'); <a name="noConflict" /> ### noConflict() -Changes the value of async back to its original value, returning a reference to the -async object. +Changes the value of `async` back to its original value, returning a reference to the +`async` object. diff --git a/component.json b/component.json new file mode 100644 index 0000000..bbb0115 --- /dev/null +++ b/component.json @@ -0,0 +1,11 @@ +{ + "name": "async", + "repo": "caolan/async", + "description": "Higher-order functions and common patterns for asynchronous code", + "version": "0.1.23", + "keywords": [], + "dependencies": {}, + "development": {}, + "main": "lib/async.js", + "scripts": [ "lib/async.js" ] +} diff --git a/lib/async.js b/lib/async.js index 14d20cc..04f7246 100755 --- a/lib/async.js +++ b/lib/async.js @@ -1,3 +1,4 @@ +/*jshint onevar: false, indent:4 */ /*global setImmediate: false, setTimeout: false, console: false */ (function () { @@ -27,6 +28,10 @@ //// cross-browser compatiblity functions //// + var _isArray = Array.isArray || function (obj) { + return toString.call(obj) === '[object Array]'; + }; + var _each = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator); @@ -75,19 +80,30 @@ //// nextTick implementation with browser-compatible fallback //// if (typeof process === 'undefined' || !(process.nextTick)) { if (typeof setImmediate === 'function') { - async.setImmediate = setImmediate; - async.nextTick = setImmediate; + async.nextTick = function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + async.setImmediate = async.nextTick; } else { - async.setImmediate = async.nextTick; async.nextTick = function (fn) { setTimeout(fn, 0); }; + async.setImmediate = async.nextTick; } } else { async.nextTick = process.nextTick; - async.setImmediate = setImmediate; + if (typeof setImmediate !== 'undefined') { + async.setImmediate = function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + } + else { + async.setImmediate = async.nextTick; + } } async.each = function (arr, iterator, callback) { @@ -97,19 +113,20 @@ } var completed = 0; _each(arr, function (x) { - iterator(x, only_once(function (err) { - if (err) { - callback(err); - callback = function () {}; - } - else { - completed += 1; - if (completed >= arr.length) { - callback(null); - } - } - })); + iterator(x, only_once(done) ); }); + function done(err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + } + } }; async.forEach = async.each; @@ -411,8 +428,11 @@ addListener(function () { if (_keys(results).length === keys.length) { - callback(null, results); + var theCallback = callback; + // prevent final callback from calling itself if it errors callback = function () {}; + + theCallback(null, results); } }); @@ -461,7 +481,7 @@ async.waterfall = function (tasks, callback) { callback = callback || function () {}; - if (tasks.constructor !== Array) { + if (!_isArray(tasks)) { var err = new Error('First argument to waterfall must be an array of functions'); return callback(err); } @@ -494,7 +514,7 @@ var _parallel = function(eachfn, tasks, callback) { callback = callback || function () {}; - if (tasks.constructor === Array) { + if (_isArray(tasks)) { eachfn.map(tasks, function (fn, callback) { if (fn) { fn(function (err) { @@ -534,7 +554,7 @@ async.series = function (tasks, callback) { callback = callback || function () {}; - if (tasks.constructor === Array) { + if (_isArray(tasks)) { async.mapSeries(tasks, function (fn, callback) { if (fn) { fn(function (err) { @@ -622,7 +642,8 @@ if (err) { return callback(err); } - if (test()) { + var args = Array.prototype.slice.call(arguments, 1); + if (test.apply(null, args)) { async.doWhilst(iterator, test, callback); } else { @@ -650,7 +671,8 @@ if (err) { return callback(err); } - if (!test()) { + var args = Array.prototype.slice.call(arguments, 1); + if (!test.apply(null, args)) { async.doUntil(iterator, test, callback); } else { @@ -664,9 +686,17 @@ concurrency = 1; } function _insert(q, data, pos, callback) { - if(data.constructor !== Array) { + if (!_isArray(data)) { data = [data]; } + if(data.length == 0) { + // call drain immediately if there are no tasks + return async.setImmediate(function() { + if (q.drain) { + q.drain(); + } + }); + } _each(data, function(task) { var item = { data: task, @@ -740,8 +770,9 @@ saturated: null, empty: null, drain: null, + drained: true, push: function (data, callback) { - if(data.constructor !== Array) { + if (!_isArray(data)) { data = [data]; } _each(data, function(task) { @@ -749,6 +780,7 @@ data: task, callback: typeof callback === 'function' ? callback : null }); + cargo.drained = false; if (cargo.saturated && tasks.length === payload) { cargo.saturated(); } @@ -758,13 +790,14 @@ process: function process() { if (working) return; if (tasks.length === 0) { - if(cargo.drain) cargo.drain(); + if(cargo.drain && !cargo.drained) cargo.drain(); + cargo.drained = true; return; } var ts = typeof payload === 'number' ? tasks.splice(0, payload) - : tasks.splice(0); + : tasks.splice(0, tasks.length); var ds = _map(ts, function (task) { return task.data; @@ -832,7 +865,9 @@ var callback = args.pop(); var key = hasher.apply(null, args); if (key in memo) { - callback.apply(null, memo[key]); + async.nextTick(function () { + callback.apply(null, memo[key]); + }); } else if (key in queues) { queues[key].push(callback); @@ -876,8 +911,8 @@ return async.mapSeries(counter, iterator, callback); }; - async.compose = function (/* functions... */) { - var fns = Array.prototype.reverse.call(arguments); + async.seq = function (/* functions... */) { + var fns = arguments; return function () { var that = this; var args = Array.prototype.slice.call(arguments); @@ -895,6 +930,10 @@ }; }; + async.compose = function (/* functions... */) { + return async.seq.apply(null, Array.prototype.reverse.call(arguments)); + }; + var _applyEach = function (eachfn, fns /*args...*/) { var go = function () { var that = this; @@ -929,16 +968,16 @@ next(); }; + // Node.js + if (typeof module !== 'undefined' && module.exports) { + module.exports = async; + } // AMD / RequireJS - if (typeof define !== 'undefined' && define.amd) { + else if (typeof define !== 'undefined' && define.amd) { define([], function () { return async; }); } - // Node.js - else if (typeof module !== 'undefined' && module.exports) { - module.exports = async; - } // included directly via <script> tag else { root.async = async; diff --git a/package.json b/package.json index 9fa85b5..e0baa66 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,18 @@ "description": "Higher-order functions and common patterns for asynchronous code", "main": "./lib/async", "author": "Caolan McMahon", - "version": "0.2.6", + "version": "0.2.10", "repository" : { "type" : "git", - "url" : "http://github.com/caolan/async.git" + "url" : "https://github.com/caolan/async.git" }, "bugs" : { - "url" : "http://github.com/caolan/async/issues" + "url" : "https://github.com/caolan/async/issues" }, "licenses" : [ { "type" : "MIT", - "url" : "http://github.com/caolan/async/raw/master/LICENSE" + "url" : "https://github.com/caolan/async/raw/master/LICENSE" } ], "devDependencies": { diff --git a/test/test-async.js b/test/test-async.js index ff401e7..fdcac5f 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -261,6 +261,93 @@ exports['compose binding'] = function (test) { }); }; +exports['seq'] = function (test) { + test.expect(4); + var add2 = function (n, cb) { + test.equal(n, 3); + setTimeout(function () { + cb(null, n + 2); + }, 50); + }; + var mul3 = function (n, cb) { + test.equal(n, 5); + setTimeout(function () { + cb(null, n * 3); + }, 15); + }; + var add1 = function (n, cb) { + test.equal(n, 15); + setTimeout(function () { + cb(null, n + 1); + }, 100); + }; + var add2mul3add1 = async.seq(add2, mul3, add1); + add2mul3add1(3, function (err, result) { + if (err) { + return test.done(err); + } + test.equal(result, 16); + test.done(); + }); +}; + +exports['seq error'] = function (test) { + test.expect(3); + var testerr = new Error('test'); + + var add2 = function (n, cb) { + test.equal(n, 3); + setTimeout(function () { + cb(null, n + 2); + }, 50); + }; + var mul3 = function (n, cb) { + test.equal(n, 5); + setTimeout(function () { + cb(testerr); + }, 15); + }; + var add1 = function (n, cb) { + test.ok(false, 'add1 should not get called'); + setTimeout(function () { + cb(null, n + 1); + }, 100); + }; + var add2mul3add1 = async.seq(add2, mul3, add1); + add2mul3add1(3, function (err, result) { + test.equal(err, testerr); + test.done(); + }); +}; + +exports['seq binding'] = function (test) { + test.expect(4); + var testerr = new Error('test'); + var testcontext = {name: 'foo'}; + + var add2 = function (n, cb) { + test.equal(this, testcontext); + setTimeout(function () { + cb(null, n + 2); + }, 50); + }; + var mul3 = function (n, cb) { + test.equal(this, testcontext); + setTimeout(function () { + cb(null, n * 3); + }, 15); + }; + var add2mul3 = async.seq(add2, mul3); + add2mul3.call(testcontext, 3, function (err, result) { + if (err) { + return test.done(err); + } + test.equal(this, testcontext); + test.equal(result, 15); + test.done(); + }); +}; + exports['auto'] = function(test){ var callOrder = []; var testdata = [{test: 'test'}]; @@ -431,6 +518,42 @@ exports['auto removeListener has side effect on loop iterator'] = function(test) }); }; +// Issue 410 on github: https://github.com/caolan/async/issues/410 +exports['auto calls callback multiple times'] = function(test) { + if (typeof process === 'undefined') { + // node only test + return; + } + var finalCallCount = 0; + var domain = require('domain').create(); + domain.on('error', function (e) { + // ignore test error + if (!e._test_error) { + return test.done(e); + } + }); + domain.run(function () { + async.auto({ + task1: function(callback) { callback(null); }, + task2: ['task1', function(callback) { callback(null); }] + }, + + // Error throwing final callback. This should only run once + function(err) { + finalCallCount++; + var e = new Error("An error"); + e._test_error = true; + throw e; + }); + }); + setTimeout(function () { + test.equal(finalCallCount, 1, + "Final auto callback should only be called once" + ); + test.done(); + }, 10); +}; + exports['waterfall'] = function(test){ test.expect(6); var call_order = []; @@ -1711,6 +1834,34 @@ exports['doUntil'] = function (test) { ); }; +exports['doUntil callback params'] = function (test) { + var call_order = []; + var count = 0; + async.doUntil( + function (cb) { + debugger + call_order.push(['iterator', count]); + count++; + cb(null, count); + }, + function (c) { + call_order.push(['test', c]); + return (c == 5); + }, + function (err) { + test.same(call_order, [ + ['iterator', 0], ['test', 1], + ['iterator', 1], ['test', 2], + ['iterator', 2], ['test', 3], + ['iterator', 3], ['test', 4], + ['iterator', 4], ['test', 5] + ]); + test.equals(count, 5); + test.done(); + } + ); +}; + exports['whilst'] = function (test) { var call_order = []; @@ -1769,6 +1920,35 @@ exports['doWhilst'] = function (test) { ); }; +exports['doWhilst callback params'] = function (test) { + var call_order = []; + + var count = 0; + async.doWhilst( + function (cb) { + call_order.push(['iterator', count]); + count++; + cb(null, count); + }, + function (c) { + call_order.push(['test', c]); + return (c < 5); + }, + function (err) { + debugger + test.same(call_order, [ + ['iterator', 0], ['test', 1], + ['iterator', 1], ['test', 2], + ['iterator', 2], ['test', 3], + ['iterator', 3], ['test', 4], + ['iterator', 4], ['test', 5] + ]); + test.equals(count, 5); + test.done(); + } + ); +}; + exports['queue'] = function (test) { var call_order = [], delays = [160,80,240,80]; @@ -2195,28 +2375,115 @@ exports['cargo bulk task'] = function (test) { }, 800); }; +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(); + }, 500); +}; + +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++; + } + + loadCargo(); + setTimeout(loadCargo, 500); + + setTimeout(function(){ + test.equal(drainCounter, 2); + test.done(); + }, 1000); +}; + exports['memoize'] = function (test) { test.expect(4); var call_order = []; var fn = function (arg1, arg2, callback) { - call_order.push(['fn', arg1, arg2]); - callback(null, arg1 + arg2); + async.setImmediate(function () { + call_order.push(['fn', arg1, arg2]); + callback(null, arg1 + arg2); + }); }; var fn2 = async.memoize(fn); fn2(1, 2, function (err, result) { test.equal(result, 3); + fn2(1, 2, function (err, result) { + test.equal(result, 3); + fn2(2, 2, function (err, result) { + test.equal(result, 4); + test.same(call_order, [['fn',1,2], ['fn',2,2]]); + test.done(); + }); + }); }); +}; + +exports['memoize maintains asynchrony'] = function (test) { + test.expect(3); + var call_order = []; + + var fn = function (arg1, arg2, callback) { + call_order.push(['fn', arg1, arg2]); + async.setImmediate(function () { + call_order.push(['cb', arg1, arg2]); + callback(null, arg1 + arg2); + }); + }; + + var fn2 = async.memoize(fn); fn2(1, 2, function (err, result) { test.equal(result, 3); - }); - fn2(2, 2, function (err, result) { - test.equal(result, 4); - }); - - test.same(call_order, [['fn',1,2], ['fn',2,2]]); - test.done(); + fn2(1, 2, function (err, result) { + test.equal(result, 3); + async.nextTick(memoize_done); + call_order.push('tick3'); + }); + call_order.push('tick2'); + }); + call_order.push('tick1'); + + function memoize_done() { + var async_call_order = [ + ['fn',1,2], // initial async call + 'tick1', // async caller + ['cb',1,2], // async callback + // ['fn',1,2], // memoized // memoized async body + 'tick2', // handler for first async call + // ['cb',1,2], // memoized // memoized async response body + 'tick3' // handler for memoized async call + ]; + test.same(call_order, async_call_order); + test.done(); + } }; exports['unmemoize'] = function(test) { @@ -2225,24 +2492,24 @@ exports['unmemoize'] = function(test) { var fn = function (arg1, arg2, callback) { call_order.push(['fn', arg1, arg2]); - callback(null, arg1 + arg2); + async.setImmediate(function () { + callback(null, arg1 + arg2); + }); }; var fn2 = async.memoize(fn); var fn3 = async.unmemoize(fn2); fn3(1, 2, function (err, result) { test.equal(result, 3); + fn3(1, 2, function (err, result) { + test.equal(result, 3); + fn3(2, 2, function (err, result) { + test.equal(result, 4); + test.same(call_order, [['fn',1,2], ['fn',1,2], ['fn',2,2]]); + test.done(); + }); + }); }); - fn3(1, 2, function (err, result) { - test.equal(result, 3); - }); - fn3(2, 2, function (err, result) { - test.equal(result, 4); - }); - - test.same(call_order, [['fn',1,2], ['fn',1,2], ['fn',2,2]]); - - test.done(); } exports['unmemoize a not memoized function'] = function(test) { @@ -2302,11 +2569,11 @@ exports['memoize custom hash function'] = function (test) { }); fn2(1, 2, function (err, result) { test.equal(result, 3); + fn2(2, 2, function (err, result) { + test.equal(result, 3); + test.done(); + }); }); - fn2(2, 2, function (err, result) { - test.equal(result, 3); - }); - test.done(); }; exports['memoize manually added memo value'] = function (test) { @@ -2436,3 +2703,25 @@ exports['queue events'] = function(test) { q.push('poo', function () {calls.push('poo cb');}); q.push('moo', function () {calls.push('moo cb');}); }; + +exports['queue empty'] = function(test) { + var calls = []; + var q = async.queue(function(task, cb) { + // nop + calls.push('process ' + task); + async.setImmediate(cb); + }, 3); + + q.drain = function() { + test.ok( + q.length() == 0 && q.running() == 0, + 'queue should be empty now and no more workers should be running' + ); + calls.push('drain'); + test.same(calls, [ + 'drain' + ]); + test.done(); + }; + q.push([]); +}; |