├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── images └── insync.png ├── lib ├── collection.js ├── flow.js ├── index.js └── util.js ├── package.json └── test ├── insync.js └── transformer.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-hapi", 3 | "rules": { 4 | "hapi/hapi-scope-start": [1, "allow-one-liners", 2], 5 | "hapi/no-shadow-relaxed": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | *.idea 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Deployed apps should consider commenting this line out: 25 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 26 | node_modules 27 | 28 | # Ignore Mac DS Store 29 | .DS_Store 30 | 31 | # Ignore babel output 32 | build/* 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Do not include lib/ here because npm prepublish is also called on local npm install 2 | images/* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "5" 8 | - "iojs" 9 | - "iojs-v1" 10 | - "iojs-v2" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Continuation Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Insync](https://raw.github.com/continuationlabs/insync/master/images/insync.png) 2 | 3 | [![Current Version](https://img.shields.io/npm/v/insync.svg)](https://www.npmjs.org/package/insync) 4 | [![Build Status via Travis CI](https://travis-ci.org/continuationlabs/insync.svg?branch=master)](https://travis-ci.org/continuationlabs/insync) 5 | ![Dependencies](http://img.shields.io/david/continuationlabs/insync.svg) 6 | 7 | Port of [async](https://github.com/caolan/async) that focuses strictly on Node.js. This project started as a direct fork of async. Feature parity with async was the goal for version 1.0.0. 8 | 9 | insync is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Unlike async, insync is not intended to work in any environment other than Node.js. However, because insync does not have any dependencies, it should be trivial to use with Browserify. Of course, you could use async too. 10 | 11 | insync provides a number of functions that include the usual 'functional' suspects (`map`, `reduce`, `filter`, `each`, etc.) as well as some common patterns for asynchronous control flow (`parallel`, `series`, `waterfall`, etc.). All these functions assume you follow the Node.js convention of providing a single callback as the last argument of your asynchronous function. 12 | 13 | ## Why Fork async? 14 | 15 | async is a fantastic module, one of the most popular on npm. So why fork it? The first reason was for a fun side project, but there is more to it: 16 | 17 | - At the time of writing (Nov 8, 2014), there are 62 open issues and 46 open pull requests, while the last commit to the repo occurred on May 27, 2014. The goal is for insync to grow an active community. 18 | - Despite it's widespread use, async is only at version 0.9.0. While not a huge deal, the majority of the npm ecosystem follows semantic versioning, making 1.0.0 is a big deal to some people. 19 | - Due to its support for browser environments, async has a decent amount of overhead in the code. Examples of this include checks for the existence of array extras such as `map()` and `forEach()` and shims for `setImmediate()` and `nextTick()`. Because insync is focused solely on node.js, these things are known to exist. 20 | - Related to the previous point - because insync targets node, code can be optimized for v8. This is a work in progress. 21 | 22 | ## Contributing 23 | 24 | insync follows the [hapijs style guide](https://github.com/hapijs/hapi/blob/master/docs/Style.md). Contributions must also adhere to the style guide. [lab](https://github.com/hapijs/lab) framework is used to run tests (via `npm test`), lint the code, and enforce code coverage. Contributions must pass linting and maintain 100% code coverage. If a contribution is a bug fix, at least one test should be added to prevent regressions. 25 | 26 | ## Quick Examples 27 | 28 | ```javascript 29 | Insync.map(['file1', 'file2', 'file3'], fs.stat, function (err, results) { 30 | 31 | // results is now an array of stats for each file 32 | }); 33 | 34 | Insync.filter(['file1', 'file2', 'file3'], fs.access, function (err, results) { 35 | 36 | // results now equals an array of the existing files 37 | }); 38 | 39 | Insync.parallel([ 40 | function () { ... }, 41 | function () { ... } 42 | ], callback); 43 | 44 | Insync.series([ 45 | function () { ... }, 46 | function () { ... } 47 | ]); 48 | ``` 49 | 50 | There are many more functions available so take a look at the docs below for a full list. This module aims to be comprehensive, so if you feel anything is missing please create a GitHub issue for it. 51 | 52 | ## Common Pitfalls 53 | 54 | 55 | ### Synchronous iterators 56 | 57 | If you get an error like `RangeError: Maximum call stack size exceeded` while using insync, you are likely using a synchronous iterator. Invoking many synchronous callbacks will quickly overflow the stack. If you run into this issue, defer your callback using `setImmediate()`. 58 | 59 | This can also arise by accident if you callback early in certain cases: 60 | 61 | ```javascript 62 | Insync.eachSeries(hugeArray, function iterator(item, callback) { 63 | 64 | if (inCache(item)) { 65 | callback(null, cache[item]); // If many items are cached, you'll overflow 66 | } 67 | else { 68 | doSomeIO(item, callback); 69 | } 70 | }, function done() { 71 | 72 | // ... 73 | }); 74 | ``` 75 | 76 | Just change it to: 77 | 78 | ```javascript 79 | Insync.eachSeries(hugeArray, function iterator(item, callback) { 80 | 81 | if (inCache(item)) { 82 | setImmediate(function () { 83 | 84 | callback(null, cache[item]); 85 | }); 86 | } 87 | else { 88 | doSomeIO(item, callback); 89 | } 90 | // ... 91 | ``` 92 | 93 | If the event loop is still a bit nebulous, check out [this article](http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/) or [this talk](http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html). 94 | 95 | ### Binding a context to an iterator 96 | 97 | This section is really about `bind`, not about insync. If you are wondering how to make insync 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: 98 | 99 | ```javascript 100 | // Here is a simple object with an (unnecessarily roundabout) squaring method 101 | var AsyncSquaringLibrary = { 102 | squareExponent: 2, 103 | square: function (number, callback) { 104 | 105 | var result = Math.pow(number, this.squareExponent); 106 | 107 | setTimeout(function () { 108 | 109 | callback(null, result); 110 | }, 200); 111 | } 112 | }; 113 | 114 | Insync.map([1, 2, 3], AsyncSquaringLibrary.square, function (err, result) { 115 | 116 | // result is [NaN, NaN, NaN] 117 | // This fails because the `this.squareExponent` expression in the square 118 | // function is not evaluated in the context of AsyncSquaringLibrary, and is 119 | // therefore undefined. 120 | }); 121 | 122 | Insync.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function (err, result) { 123 | 124 | // result is [1, 4, 9] 125 | // With the help of bind we can attach a context to the iterator before 126 | // passing it to insync. Now the square function will be executed in its 127 | // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent` 128 | // will be as expected. 129 | }); 130 | ``` 131 | 132 | ## Download 133 | 134 | The source is available for download from [GitHub](http://github.com/continuationlabs/insync). Alternatively, you can install using `npm`: 135 | 136 | npm install insync 137 | 138 | ## Documentation 139 | 140 | ### Collections 141 | 142 | * [`each`](#each) 143 | * [`eachSeries`](#eachSeries) 144 | * [`eachLimit`](#eachLimit) 145 | * [`forEachOf`](#forEachOf) 146 | * [`forEachOfSeries`](#forEachOfSeries) 147 | * [`forEachOfLimit`](#forEachOfLimit) 148 | * [`map`](#map) 149 | * [`mapSeries`](#mapSeries) 150 | * [`mapLimit`](#mapLimit) 151 | * [`filter`](#filter) 152 | * [`filterSeries`](#filterSeries) 153 | * [`reject`](#reject) 154 | * [`rejectSeries`](#rejectSeries) 155 | * [`reduce`](#reduce) 156 | * [`reduceRight`](#reduceRight) 157 | * [`detect`](#detect) 158 | * [`detectSeries`](#detectSeries) 159 | * [`sortBy`](#sortBy) 160 | * [`some`](#some) 161 | * [`every`](#every) 162 | * [`concat`](#concat) 163 | * [`concatSeries`](#concatSeries) 164 | 165 | ### Control Flow 166 | 167 | * [`series`](#seriestasks-callback) 168 | * [`parallel`](#parallel) 169 | * [`parallelLimit`](#parallellimittasks-limit-callback) 170 | * [`whilst`](#whilst) 171 | * [`doWhilst`](#doWhilst) 172 | * [`until`](#until) 173 | * [`doUntil`](#doUntil) 174 | * [`forever`](#forever) 175 | * [`waterfall`](#waterfall) 176 | * [`compose`](#compose) 177 | * [`seq`](#seq) 178 | * [`applyEach`](#applyEach) 179 | * [`applyEachSeries`](#applyEachSeries) 180 | * [`queue`](#queue) 181 | * [`priorityQueue`](#priorityQueue) 182 | * [`cargo`](#cargo) 183 | * [`auto`](#auto) 184 | * [`retry`](#retry) 185 | * [`iterator`](#iterator) 186 | * [`apply`](#apply) 187 | * [`times`](#times) 188 | * [`timesSeries`](#timesSeries) 189 | * [`timesLimit`](#timesLimit) 190 | 191 | ### Utils 192 | 193 | * [`ensureAsync`](#ensureAsync) 194 | * [`memoize`](#memoize) 195 | * [`unmemoize`](#unmemoize) 196 | * [`log`](#log) 197 | * [`dir`](#dir) 198 | 199 | ## Collections 200 | 201 | 202 | 203 | ### each(arr, iterator[, callback]) 204 | 205 | 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. 206 | 207 | Note, that since this function applies `iterator` to each item in parallel, there is no guarantee that the iterator functions will complete in order. 208 | 209 | __Arguments__ 210 | 211 | * `arr` - An array to iterate over. 212 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 213 | The iterator is passed a `callback(err)` which must be called once it has 214 | completed. If no error has occurred, the `callback` should be run without 215 | arguments or with an explicit `null` argument. 216 | * `callback(err)` - A callback which is called when all `iterator` functions 217 | have finished, or an error occurs. Optional. 218 | 219 | __Examples__ 220 | 221 | ```javascript 222 | // assuming openFiles is an array of file names and saveFile is a function 223 | // to save the modified contents of that file: 224 | 225 | Insync.each(openFiles, saveFile, function (err) { 226 | 227 | // if any of the saves produced an error, err would equal that error 228 | }); 229 | ``` 230 | 231 | ```javascript 232 | // assuming openFiles is an array of file names 233 | 234 | Insync.each(openFiles, function (file, callback) { 235 | 236 | // Perform operation on file here. 237 | console.log('Processing file ' + file); 238 | 239 | if (file.length > 32) { 240 | console.log('This file name is too long'); 241 | callback(new Error('File name too long')); 242 | } else { 243 | // Do work to process file here 244 | console.log('File processed'); 245 | callback(); 246 | } 247 | }, function (err) { 248 | 249 | // If any of the file processing produced an error, err would equal that error 250 | if (err) { 251 | // One of the iterations produced an error. 252 | // All processing will now stop. 253 | console.log('A file failed to process'); 254 | } else { 255 | console.log('All files have been processed successfully'); 256 | } 257 | }); 258 | ``` 259 | 260 | --------------------------------------- 261 | 262 | 263 | 264 | ### eachSeries(arr, iterator[, callback]) 265 | 266 | 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. 267 | 268 | --------------------------------------- 269 | 270 | 271 | 272 | ### eachLimit(arr, limit, iterator[, callback]) 273 | 274 | The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously running at any time. 275 | 276 | 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. 277 | 278 | __Arguments__ 279 | 280 | * `arr` - An array to iterate over. 281 | * `limit` - The maximum number of `iterator`s to run at any time. 282 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 283 | The iterator is passed a `callback(err)` which must be called once it has 284 | completed. If no error has occurred, the callback should be run without 285 | arguments or with an explicit `null` argument. 286 | * `callback(err)` - A callback which is called when all `iterator` functions 287 | have finished, or an error occurs. Optional. 288 | 289 | __Example__ 290 | 291 | ```javascript 292 | // Assume documents is an array of JSON objects and requestApi is a 293 | // function that interacts with a rate-limited REST api. 294 | 295 | Insync.eachLimit(documents, 20, requestApi, function (err) { 296 | 297 | // If any of the saves produced an error, err would equal that error 298 | }); 299 | ``` 300 | 301 | --------------------------------------- 302 | 303 | 304 | 305 | 306 | ### forEachOf(obj, iterator[, callback]) 307 | 308 | Like `each`, except that it iterates over objects, and passes the key as the second argument to the iterator. 309 | 310 | __Arguments__ 311 | 312 | * `obj` - An object to iterate over. 313 | * `iterator(item, key, callback)` - A function to apply to each key in `obj`. `key` is the item's key. The iterator is passed a `callback(err)` which must be called once it has completed. 314 | * `callback(err)` - A callback which is called when all `iterator` functions have finished, or an error occurs. Optional. 315 | 316 | __Example__ 317 | 318 | ```javascript 319 | var Fs = require('fs'); 320 | var obj = { 321 | dev: '/dev.json', 322 | prod: '/prod.json' 323 | }; 324 | var configs = {}; 325 | 326 | Insync.forEachOf(obj, function (value, key, callback) { 327 | 328 | Fs.readFile(__dirname + value, 'utf8', function (err, data) { 329 | 330 | if (err) { 331 | return callback(err); 332 | } 333 | 334 | try { 335 | configs[key] = JSON.parse(data); 336 | } 337 | catch (e) { 338 | return callback(e); 339 | } 340 | 341 | callback(); 342 | }); 343 | }, function (err) { 344 | 345 | // Handle possible error 346 | }); 347 | ``` 348 | 349 | --------------------------------------- 350 | 351 | 352 | 353 | 354 | ### forEachOfSeries(obj, iterator[, callback]) 355 | 356 | Like [`forEachOf`](#forEachOf), except only one `iterator` is run at a time. The order of execution is not guaranteed, as object key ordering is not guaranteed. 357 | 358 | --------------------------------------- 359 | 360 | 361 | 362 | 363 | ### forEachOfLimit(obj, limit, iterator[, callback]) 364 | 365 | Like [`forEachOf`](#forEachOf), except the number of `iterator`s running at a given time is controlled by `limit`. 366 | 367 | --------------------------------------- 368 | 369 | 370 | ### map(arr, iterator[, callback]) 371 | 372 | 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 two arguments: an `error`, and the transformed item from `arr`. If `iterator` passes an error to its callback, the main `callback` (for the `map` function) is immediately called with the error. 373 | 374 | Note, that since this function applies the `iterator` to each item in parallel, 375 | there is no guarantee that the `iterator` functions will complete in order. 376 | However, the results array will be in the same order as the original `arr`. 377 | 378 | __Arguments__ 379 | 380 | * `arr` - An array to iterate over. 381 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 382 | The iterator is passed a `callback(err, transformed)` which must be called once 383 | it has completed with an error (which can be `null`) and a transformed item. 384 | * `callback(err, results)` - A callback which is called when all `iterator` 385 | functions have finished, or an error occurs. Results is an array of the 386 | transformed items from the `arr`. Optional. 387 | 388 | __Example__ 389 | 390 | ```javascript 391 | Insync.map(['file1', 'file2', 'file3'], fs.stat, function (err, results) { 392 | 393 | // Results is now an array of stats for each file 394 | }); 395 | ``` 396 | 397 | --------------------------------------- 398 | 399 | 400 | ### mapSeries(arr, iterator[, callback]) 401 | 402 | 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. 403 | 404 | --------------------------------------- 405 | 406 | 407 | ### mapLimit(arr, limit, iterator[, callback]) 408 | 409 | The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously running at any time. 410 | 411 | 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. 412 | 413 | __Arguments__ 414 | 415 | * `arr` - An array to iterate over. 416 | * `limit` - The maximum number of `iterator`s to run at any time. 417 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 418 | The iterator is passed a `callback(err, transformed)` which must be called once 419 | it has completed with an error (which can be `null`) and a transformed item. 420 | * `callback(err, results)` - A callback which is called when all `iterator` 421 | calls have finished, or an error occurs. The result is an array of the 422 | transformed items from the original `arr`. Optional. 423 | 424 | __Example__ 425 | 426 | ```javascript 427 | Insync.mapLimit(['file1', 'file2', 'file3'], 1, fs.stat, function (err, results) { 428 | 429 | // Results is now an array of stats for each file 430 | }); 431 | ``` 432 | 433 | --------------------------------------- 434 | 435 | 436 | 437 | ### filter(arr, iterator[, callback]) 438 | 439 | __Alias:__ `select` 440 | 441 | Returns a new array of all the values in `arr` which pass an asynchronous truth test. This operation is performed in parallel, but the results array will be in the same order as the original. 442 | 443 | __Arguments__ 444 | 445 | * `arr` - An array to iterate over. 446 | * `iterator(item, callback)` - A truth test to apply to each item in `arr`. 447 | The `iterator` is passed a `callback(err, truthValue)`, which must be called with a 448 | boolean argument once it has completed. 449 | * `callback(err, results)` - A callback which is called after all the `iterator` 450 | functions have finished. Optional. 451 | 452 | __Example__ 453 | 454 | ```javascript 455 | Insync.filter(['file1', 'file2', 'file3'], fs.access, function (err, results) { 456 | 457 | // Results now equals an array of the existing files 458 | }); 459 | ``` 460 | 461 | --------------------------------------- 462 | 463 | 464 | 465 | ### filterSeries(arr, iterator[, callback]) 466 | 467 | __Alias:__ `selectSeries` 468 | 469 | 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. 470 | 471 | --------------------------------------- 472 | 473 | 474 | ### reject(arr, iterator[, callback]) 475 | 476 | The opposite of [`filter`](#filter). Removes values that pass an asynchronous truth test. 477 | 478 | --------------------------------------- 479 | 480 | 481 | ### rejectSeries(arr, iterator[, callback]) 482 | 483 | The same as [`reject`](#reject), only the `iterator` is applied to each item in `arr` in series. 484 | 485 | --------------------------------------- 486 | 487 | 488 | ### reduce(arr, memo, iterator[, callback]) 489 | 490 | __Aliases:__ `inject`, `foldl` 491 | 492 | Reduces `arr` into a single value using an asynchronous `iterator` to return each successive step. `memo` is the initial state of the reduction. This function only operates in series. 493 | 494 | 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 asynchronous; if you can get the data before reducing it, then it's probably a good idea to do so. 495 | 496 | __Arguments__ 497 | 498 | * `arr` - An array to iterate over. 499 | * `memo` - The initial state of the reduction. 500 | * `iterator(memo, item, callback)` - A function applied to each item in the 501 | array to produce the next step in the reduction. The `iterator` is passed a 502 | `callback(err, reduction)` which accepts an optional error as its first 503 | argument, and the state of the reduction as the second. If an error is 504 | passed to the callback, the reduction is stopped and the main `callback` is 505 | immediately called with the error. 506 | * `callback(err, result)` - A callback which is called after all the `iterator` 507 | functions have finished. Result is the reduced value. Optional. 508 | 509 | __Example__ 510 | 511 | ```javascript 512 | Insync.reduce([1, 2, 3], 0, function (memo, item, callback) { 513 | 514 | // Pointless async: 515 | process.nextTick(function () { 516 | 517 | callback(null, memo + item); 518 | }); 519 | }, function (err, result) { 520 | 521 | // Result is now equal to the last value of memo, which is 6 522 | }); 523 | ``` 524 | 525 | --------------------------------------- 526 | 527 | 528 | ### reduceRight(arr, memo, iterator[, callback]) 529 | 530 | __Alias:__ `foldr` 531 | 532 | Same as [`reduce`](#reduce), only operates on `arr` in reverse order. 533 | 534 | --------------------------------------- 535 | 536 | 537 | ### detect(arr, iterator[, callback]) 538 | 539 | Returns the first value in `arr` that passes an asynchronous 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. 540 | 541 | If order within the original `arr` is important, then look at [`detectSeries`](#detectSeries). 542 | 543 | __Arguments__ 544 | 545 | * `arr` - An array to iterate over. 546 | * `iterator(item, callback)` - A truth test to apply to each item in `arr`. 547 | The iterator is passed a `callback(err, truthValue)` which must be called with a 548 | boolean argument once it has completed. 549 | * `callback(err, result)` - A callback which is called as soon as any iterator returns 550 | `true`, or after all the `iterator` functions have finished. Result will be 551 | the first item in the array that passes the truth test (iterator) or the 552 | value `undefined` if none passed. Optional. 553 | 554 | __Example__ 555 | 556 | ```javascript 557 | Insync.detect(['file1', 'file2', 'file3'], fs.access, function (err, result) { 558 | 559 | // Result now equals the first file in the list that exists 560 | }); 561 | ``` 562 | 563 | --------------------------------------- 564 | 565 | 566 | ### detectSeries(arr, iterator[, callback]) 567 | 568 | 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 569 | terms of array order) that passes the truth test. 570 | 571 | --------------------------------------- 572 | 573 | 574 | ### sortBy(arr, iterator[, callback]) 575 | 576 | Sorts a list by the results of running each `arr` value through an asynchronous `iterator`. 577 | 578 | __Arguments__ 579 | 580 | * `arr` - An array to iterate over. 581 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 582 | The iterator is passed a `callback(err, sortValue)` which must be called once it 583 | has completed with an error (which can be `null`) and a value to use as the sort 584 | criteria. 585 | * `callback(err, results)` - A callback which is called after all the `iterator` 586 | functions have finished, or an error occurs. Results is the items from 587 | the original `arr` sorted by the values returned by the `iterator` calls. Optional. 588 | 589 | __Example__ 590 | 591 | ```javascript 592 | Insync.sortBy(['file1', 'file2', 'file3'], function (file, callback) { 593 | 594 | fs.stat(file, function (err, stats) { 595 | 596 | callback(err, stats.mtime); 597 | }); 598 | }, function (err, results) { 599 | 600 | // Results is now the original array of files sorted by 601 | // modified date 602 | }); 603 | ``` 604 | 605 | __Sort Order__ 606 | 607 | By modifying the callback parameter the sorting order can be influenced: 608 | 609 | ```javascript 610 | // Ascending order 611 | Insync.sortBy([1, 9, 3, 5], function (x, callback) { 612 | 613 | callback(null, x); 614 | }, function (err, result) { 615 | 616 | // Result callback 617 | } ); 618 | 619 | // Descending order 620 | Insync.sortBy([1, 9, 3, 5], function (x, callback) { 621 | 622 | callback(null, x * -1); // <- x * -1 instead of x, turns the order around 623 | }, function (err, result) { 624 | 625 | // Result callback 626 | }); 627 | ``` 628 | 629 | --------------------------------------- 630 | 631 | 632 | ### some(arr, iterator[, callback]) 633 | 634 | __Alias:__ `any` 635 | 636 | Returns `true` if at least one element in the `arr` satisfies an asynchronous test. Once any iterator call returns `true`, the main `callback` is immediately called. 637 | 638 | __Arguments__ 639 | 640 | * `arr` - An array to iterate over. 641 | * `iterator(item, callback)` - A truth test to apply to each item in the array 642 | in parallel. The iterator is passed a `callback(err, truthValue)` which must be 643 | called with a boolean argument once it has completed. 644 | * `callback(err, result)` - A callback which is called as soon as any iterator returns 645 | `true`, or after all the iterator functions have finished. Result will be 646 | either `true` or `false` depending on the values of the asynchronous tests. Optional. 647 | 648 | __Example__ 649 | 650 | ```javascript 651 | Insync.some(['file1', 'file2', 'file3'], fs.access, function (result) { 652 | 653 | // If result is true then at least one of the files exists 654 | }); 655 | ``` 656 | 657 | --------------------------------------- 658 | 659 | 660 | ### every(arr, iterator[, callback]) 661 | 662 | __Alias:__ `all` 663 | 664 | Returns `true` if every element in `arr` satisfies an asynchronous test. 665 | 666 | __Arguments__ 667 | 668 | * `arr` - An array to iterate over. 669 | * `iterator(item, callback)` - A truth test to apply to each item in the array 670 | in parallel. The iterator is passed a `callback(err, truthValue)` which must be 671 | called with a boolean argument once it has completed. 672 | * `callback(err, result)` - A callback which is called after all the `iterator` 673 | functions have finished. Result will be either `true` or `false` depending on 674 | the values of the asynchronous tests. Optional. 675 | 676 | __Example__ 677 | 678 | ```javascript 679 | Insync.every(['file1', 'file2', 'file3'], fs.access, function (result) { 680 | 681 | // If result is true then every file exists 682 | }); 683 | ``` 684 | 685 | --------------------------------------- 686 | 687 | 688 | ### concat(arr, iterator[, callback]) 689 | 690 | 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 `arr` passed to the `iterator` function. 691 | 692 | __Arguments__ 693 | 694 | * `arr` - An array to iterate over. 695 | * `iterator(item, callback)` - A function to apply to each item in `arr`. 696 | The iterator is passed a `callback(err, results)` which must be called once it 697 | has completed with an error (which can be `null`) and an array of results. 698 | * `callback(err, results)` - A callback which is called after all the `iterator` 699 | functions have finished, or an error occurs. Results is an array containing 700 | the concatenated results of the `iterator` function. Optional. 701 | 702 | __Example__ 703 | 704 | ```javascript 705 | Insync.concat(['dir1', 'dir2', 'dir3'], fs.readdir, function (err, files) { 706 | 707 | // Files is now a list of filenames that exist in the three directories 708 | }); 709 | ``` 710 | 711 | --------------------------------------- 712 | 713 | 714 | ### concatSeries(arr, iterator[, callback]) 715 | 716 | Same as [`concat`](#concat), but executes in series instead of parallel. 717 | 718 | ## Control Flow 719 | 720 | 721 | ### series(tasks[, callback]) 722 | 723 | 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. Otherwise, `callback` receives an array of results when `tasks` have completed. 724 | 725 | 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 instead of an array. This can be a more readable way of handling results from [`series`](#series). 726 | 727 | **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: 728 | 729 | > The mechanics and order of enumerating the properties is not specified. 730 | 731 | 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. 732 | 733 | __Arguments__ 734 | 735 | * `tasks` - An array or object containing functions to run, each function is passed 736 | a `callback(err, result)` it must call on completion with an error `err` (which can 737 | be `null`) and an optional `result` value. 738 | * `callback(err, results)` - A callback to run once all the functions 739 | have completed. This function gets a results array (or object) containing all 740 | the result arguments passed to the `task` callbacks. Optional. 741 | 742 | __Example__ 743 | 744 | ```javascript 745 | Insync.series([ 746 | function (callback) { 747 | 748 | // Do some stuff ... 749 | callback(null, 'one'); 750 | }, 751 | function (callback) { 752 | 753 | // Do some more stuff ... 754 | callback(null, 'two'); 755 | } 756 | ], 757 | // Optional callback 758 | function (err, results) { 759 | 760 | // results is now equal to ['one', 'two'] 761 | }); 762 | 763 | 764 | // An example using an object instead of an array 765 | Insync.series({ 766 | one: function (callback) { 767 | 768 | setTimeout(function () { 769 | 770 | callback(null, 1); 771 | }, 200); 772 | }, 773 | two: function (callback) { 774 | 775 | setTimeout(function () { 776 | 777 | callback(null, 2); 778 | }, 100); 779 | } 780 | }, function (err, results) { 781 | 782 | // results is now equal to: {one: 1, two: 2} 783 | }); 784 | ``` 785 | 786 | --------------------------------------- 787 | 788 | 789 | ### parallel(tasks[, callback]) 790 | 791 | 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 792 | array. 793 | 794 | 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 instead of an array. This can be a more readable way of handling results from [`parallel`](#parallel). 795 | 796 | __Arguments__ 797 | 798 | * `tasks` - An array or object containing functions to run. Each function is passed 799 | a `callback(err, result)` which it must call on completion with an error `err` 800 | (which can be `null`) and an optional `result` value. 801 | * `callback(err, results)` - A callback to run once all the functions 802 | have completed. This function gets a results array (or object) containing all 803 | the result arguments passed to the task callbacks. Optional. 804 | 805 | __Example__ 806 | 807 | ```javascript 808 | Insync.parallel([ 809 | function (callback) { 810 | 811 | setTimeout(function () { 812 | 813 | callback(null, 'one'); 814 | }, 200); 815 | }, 816 | function (callback) { 817 | 818 | setTimeout(function () { 819 | 820 | callback(null, 'two'); 821 | }, 100); 822 | } 823 | ], 824 | // Optional callback 825 | function (err, results) { 826 | // The results array will equal ['one','two'] even though 827 | // the second function had a shorter timeout. 828 | }); 829 | 830 | 831 | // An example using an object instead of an array 832 | Insync.parallel({ 833 | one: function (callback) { 834 | 835 | setTimeout(function () { 836 | 837 | callback(null, 1); 838 | }, 200); 839 | }, 840 | two: function (callback) { 841 | 842 | setTimeout(function () { 843 | 844 | callback(null, 2); 845 | }, 100); 846 | } 847 | }, function (err, results) { 848 | // results is now equals to: {one: 1, two: 2} 849 | }); 850 | ``` 851 | 852 | --------------------------------------- 853 | 854 | 855 | ### parallelLimit(tasks, limit[, callback]) 856 | 857 | The same as [`parallel`](#parallel), only `tasks` are executed in parallel with a maximum of `limit` tasks executing at any time. 858 | 859 | 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. 860 | 861 | __Arguments__ 862 | 863 | * `tasks` - An array or object containing functions to run, each function is passed 864 | a `callback(err, result)` it must call on completion with an error `err` (which can 865 | be `null`) and an optional `result` value. 866 | * `limit` - The maximum number of `tasks` to run at any time. 867 | * `callback(err, results)` - A callback to run once all the functions 868 | have completed. This function gets a results array (or object) containing all 869 | the result arguments passed to the `task` callbacks. Optional. 870 | 871 | --------------------------------------- 872 | 873 | 874 | ### whilst(test, fn, callback) 875 | 876 | Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped, or an error occurs. 877 | 878 | __Arguments__ 879 | 880 | * `test()` - Synchronous truth test to perform before each execution of `fn`. 881 | * `fn(callback)` - A function which is called each time `test` passes. The function is 882 | passed a `callback(err)`, which must be called once it has completed with an 883 | optional `err` argument. 884 | * `callback(err)` - A callback which is called after the test fails and repeated 885 | execution of `fn` has stopped. 886 | 887 | __Example__ 888 | 889 | ```javascript 890 | var count = 0; 891 | 892 | Insync.whilst( 893 | function () { return count < 5; }, 894 | function (callback) { 895 | count++; 896 | setTimeout(callback, 1000); 897 | }, 898 | function (err) { 899 | // 5 seconds have passed 900 | } 901 | ); 902 | ``` 903 | 904 | --------------------------------------- 905 | 906 | 907 | ### doWhilst(fn, test, callback) 908 | 909 | The post-check version of [`whilst`](#whilst). To reflect the difference in the order of operations, the arguments `test` and `fn` are switched. 910 | 911 | `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. 912 | 913 | --------------------------------------- 914 | 915 | 916 | ### until(test, fn, callback) 917 | 918 | Repeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped, or an error occurs. 919 | 920 | The inverse of [`whilst`](#whilst). 921 | 922 | --------------------------------------- 923 | 924 | 925 | ### doUntil(fn, test, callback) 926 | 927 | Like [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`. 928 | 929 | --------------------------------------- 930 | 931 | 932 | ### forever(fn[, errback]) 933 | 934 | Calls the asynchronous function `fn` with a callback parameter that allows it to call itself again, in series, indefinitely. 935 | 936 | If an error is passed to the callback then `errback` is called with the error, and execution stops, otherwise it will never be called. 937 | 938 | ```javascript 939 | Insync.forever( 940 | function (next) { 941 | // next is suitable for passing to things that need a callback(err [, whatever]); 942 | // It will result in this function being called again. 943 | }, 944 | function (err) { 945 | // If next is called with a value in its first parameter, it will appear 946 | // in here as 'err', and execution will stop. 947 | } 948 | ); 949 | ``` 950 | 951 | --------------------------------------- 952 | 953 | 954 | ### waterfall(tasks[, callback]) 955 | 956 | 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. 957 | 958 | __Arguments__ 959 | 960 | * `tasks` - An array of functions to run, each function is passed a 961 | `callback(err, ...results)` it must call on completion. The first 962 | argument is an error (which can be `null`) and any further arguments will be 963 | passed as arguments in order to the next task. 964 | * `callback(err, ...results)` - A callback to run once all the functions 965 | have completed. This will be passed the results of the last task's callback. Optional. 966 | 967 | __Example__ 968 | 969 | ```javascript 970 | Insync.waterfall([ 971 | function (callback) { 972 | 973 | callback(null, 'one', 'two'); 974 | }, 975 | function (arg1, arg2, callback) { 976 | 977 | // arg1 now equals 'one' and arg2 now equals 'two' 978 | callback(null, 'three'); 979 | }, 980 | function (arg1, callback) { 981 | 982 | // arg1 now equals 'three' 983 | callback(null, 'done'); 984 | } 985 | ], function (err, result) { 986 | 987 | // Result now equals 'done' 988 | }); 989 | ``` 990 | 991 | --------------------------------------- 992 | 993 | ### compose(...fns) 994 | 995 | 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. 996 | 997 | Each function is executed with the `this` binding of the composed function. 998 | 999 | __Arguments__ 1000 | 1001 | * `...fns` - The asynchronous functions to compose. 1002 | 1003 | __Example__ 1004 | 1005 | ```javascript 1006 | function add1(n, callback) { 1007 | 1008 | setTimeout(function () { 1009 | 1010 | callback(null, n + 1); 1011 | }, 10); 1012 | } 1013 | 1014 | function mul3(n, callback) { 1015 | 1016 | setTimeout(function () { 1017 | 1018 | callback(null, n * 3); 1019 | }, 10); 1020 | } 1021 | 1022 | var add1mul3 = Insync.compose(mul3, add1); 1023 | 1024 | add1mul3(4, function (err, result) { 1025 | 1026 | // result now equals 15 1027 | }); 1028 | ``` 1029 | 1030 | --------------------------------------- 1031 | 1032 | ### seq(...fns) 1033 | 1034 | Version of the [`compose`](#compose) function that is more natural to read. Each function consumes the return value of the previous function. It is equivalent of `compose()` with the arguments reversed. 1035 | 1036 | Each function is executed with the `this` binding of the composed function. 1037 | 1038 | __Arguments__ 1039 | 1040 | * `...fns` - The asynchronous functions to compose. 1041 | 1042 | 1043 | __Example__ 1044 | 1045 | ```javascript 1046 | // Requires lodash (or underscore), express3 and dresende's orm2. 1047 | // Part of an app, that fetches cats of the logged user. 1048 | // This example uses `seq` function to avoid overnesting and error 1049 | // handling clutter. 1050 | app.get('/cats', function (request, response) { 1051 | 1052 | var User = request.models.User; 1053 | 1054 | Insync.seq( 1055 | _.bind(User.get, User), // 'User.get' has signature (id, callback(err, data)) 1056 | handleError, 1057 | function (user, fn) { 1058 | 1059 | user.getCats(fn); // 'getCats' has signature (callback(err, data)) 1060 | } 1061 | )(req.session.user_id, function (err, cats) { 1062 | 1063 | if (err) { 1064 | console.error(err); 1065 | response.json({ status: 'error', message: err.message }); 1066 | } 1067 | else { 1068 | response.json({ status: 'ok', message: 'Cats found', data: cats }); 1069 | } 1070 | }); 1071 | }); 1072 | ``` 1073 | 1074 | --------------------------------------- 1075 | 1076 | ### applyEach(fns, ...args, callback) 1077 | 1078 | 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. 1079 | 1080 | __Arguments__ 1081 | 1082 | * `fns` - The asynchronous functions to call with the same arguments. 1083 | * `...args` - Any number of separate arguments to pass to the function. 1084 | * `callback` - The final argument should be the callback, called when all 1085 | functions have completed processing 1086 | 1087 | __Example__ 1088 | 1089 | ```javascript 1090 | Insync.applyEach([enableSearch, updateSchema], 'bucket', callback); 1091 | 1092 | // partial application example: 1093 | Insync.each( 1094 | buckets, 1095 | Insync.applyEach([enableSearch, updateSchema]), 1096 | callback 1097 | ); 1098 | ``` 1099 | 1100 | --------------------------------------- 1101 | 1102 | 1103 | ### applyEachSeries(arr, ...args, callback) 1104 | 1105 | The same as [`applyEach`](#applyEach) only the functions are applied in series. 1106 | 1107 | --------------------------------------- 1108 | 1109 | 1110 | ### queue(worker[, concurrency]) 1111 | 1112 | 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. 1113 | 1114 | __Arguments__ 1115 | 1116 | * `worker(task, callback)` - An asynchronous function for processing a queued 1117 | task, which must call its `callback(err)` argument when finished, with an 1118 | optional `error` as an argument. If you want to handle errors from an individual task, pass a callback to `q.push()`. 1119 | * `concurrency` - An `integer` for determining how many `worker` functions should be 1120 | run in parallel. If omitted, the concurrency defaults to 1. If the concurrency is not a positive integer, an error is thrown. Optional. 1121 | 1122 | __Queue objects__ 1123 | 1124 | The `queue` object returned by this function has the following properties and 1125 | methods: 1126 | 1127 | * `length()` - a function returning the number of items waiting to be processed. 1128 | * `started` - a function returning whether or not any items have been pushed and processed by the queue 1129 | * `running()` - a function returning the number of items currently being processed. 1130 | * `idle()` - a function returning false if there are items waiting or being processed, or true if not. 1131 | * `concurrency` - an integer for determining how many `worker` functions should be 1132 | run in parallel. This property can be changed after a `queue` is created to 1133 | alter the concurrency on-the-fly. 1134 | * `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once 1135 | the `worker` has finished processing the task. Instead of a single task, a `tasks` array 1136 | can be submitted. The respective callback is used for every task in the list. 1137 | * `unshift(task, [callback])` - add a new task to the front of the `queue`. 1138 | * `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, 1139 | and further tasks will be queued. 1140 | * `empty` - a callback that is called when the last item from the `queue` is given to a `worker`. 1141 | * `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`. 1142 | * `paused` - a boolean for determining whether the queue is in a paused state 1143 | * `pause()` - a function that pauses the processing of tasks until `resume()` is called. 1144 | * `resume()` - a function that resumes the processing of queued tasks when the queue is paused. 1145 | * `kill()` - a function that removes the `drain()` callback and empties remaining tasks from the queue, forcing it to go idle. 1146 | 1147 | __Example__ 1148 | 1149 | ```javascript 1150 | // Create a queue object with concurrency 2 1151 | 1152 | var q = Insync.queue(function (task, callback) { 1153 | 1154 | console.log('hello ' + task.name); 1155 | callback(); 1156 | }, 2); 1157 | 1158 | 1159 | // Assign a callback 1160 | q.drain = function () { 1161 | 1162 | console.log('all items have been processed'); 1163 | } 1164 | 1165 | // Add some items to the queue 1166 | 1167 | q.push({ name: 'foo' }, function (err) { 1168 | 1169 | console.log('finished processing foo'); 1170 | }); 1171 | q.push({ name: 'bar' }, function (err) { 1172 | 1173 | console.log('finished processing bar'); 1174 | }); 1175 | 1176 | // Add some items to the queue (batch-wise) 1177 | 1178 | q.push([ 1179 | { name: 'baz' }, 1180 | { name: 'bay' }, 1181 | { name: 'bax' } 1182 | ], function (err) { 1183 | 1184 | console.log('finished processing item'); 1185 | }); 1186 | 1187 | // Add some items to the front of the queue 1188 | 1189 | q.unshift({ name: 'bar' }, function (err) { 1190 | 1191 | console.log('finished processing bar'); 1192 | }); 1193 | ``` 1194 | 1195 | --------------------------------------- 1196 | 1197 | 1198 | ### priorityQueue(worker, concurrency) 1199 | 1200 | The same as [`queue`](#queue) only tasks are assigned a priority and completed in ascending priority order. There are two differences between `queue` and `priorityQueue` objects: 1201 | 1202 | * `push(task, priority[, callback])` - `priority` should be a number. If an array of 1203 | `tasks` is given, all tasks will be assigned the same priority. 1204 | * The `unshift` method was removed. 1205 | 1206 | --------------------------------------- 1207 | 1208 | 1209 | ### cargo(worker[, payload]) 1210 | 1211 | 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. 1212 | 1213 | Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966) for how `cargo` and `queue` work. 1214 | 1215 | 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. 1216 | 1217 | __Arguments__ 1218 | 1219 | * `worker(tasks, callback)` - An asynchronous function for processing an array of 1220 | queued tasks, which must call its `callback(err)` argument when finished, with 1221 | an optional `err` argument. 1222 | * `payload` - An `integer` for determining how many tasks should be 1223 | processed per round; if omitted, the default is unlimited. Optional. 1224 | 1225 | __Cargo objects__ 1226 | 1227 | The `cargo` object returned by this function has the following properties and methods: 1228 | 1229 | * `length()` - A function returning the number of items waiting to be processed. 1230 | * `payload` - An `integer` indicating how many tasks should be 1231 | processed per round. This property can be changed after a `cargo` is created to alter the payload on-the-fly. 1232 | * `push(task[, callback])` - Adds `task` to the `queue`. The callback is called 1233 | once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` 1234 | can be submitted. The respective callback is used for every task in the list. 1235 | * `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued. 1236 | * `empty` - A callback that is called when the last item from the `queue` is given to a `worker`. 1237 | * `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`. 1238 | * `idle()`, `pause()`, `resume()`, `kill()` - `cargo` inherits all of the same methods and event callbacks as [`queue`](#queue). 1239 | 1240 | __Example__ 1241 | 1242 | ```javascript 1243 | // Create a cargo object with payload 2 1244 | 1245 | var cargo = Insync.cargo(function (tasks, callback) { 1246 | 1247 | for (var i=0, il = tasks.length; i < il; ++i) { 1248 | console.log('hello ' + tasks[i].name); 1249 | } 1250 | 1251 | callback(); 1252 | }, 2); 1253 | 1254 | // Add some items 1255 | 1256 | cargo.push({ name: 'foo' }, function (err) { 1257 | 1258 | console.log('finished processing foo'); 1259 | }); 1260 | cargo.push({ name: 'bar' }, function (err) { 1261 | 1262 | console.log('finished processing bar'); 1263 | }); 1264 | cargo.push({ name: 'baz' }, function (err) { 1265 | 1266 | console.log('finished processing baz'); 1267 | }); 1268 | ``` 1269 | 1270 | --------------------------------------- 1271 | 1272 | 1273 | ### auto(tasks[, callback]) 1274 | 1275 | 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. 1276 | 1277 | 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. 1278 | 1279 | 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. 1280 | 1281 | For example, this snippet of code: 1282 | 1283 | ```javascript 1284 | Insync.auto({ 1285 | readData: Insync.apply(fs.readFile, 'data.txt', 'utf-8') 1286 | }, callback); 1287 | ``` 1288 | 1289 | will have the effect of calling `readFile` with the results object as the last argument, which will fail: 1290 | 1291 | ```javascript 1292 | fs.readFile('data.txt', 'utf-8', callback, {}); 1293 | ``` 1294 | 1295 | Instead, wrap the call to `readFile` in a function which does not forward the `results` object: 1296 | 1297 | ```javascript 1298 | Insync.auto({ 1299 | readData: function (callback, results) { 1300 | 1301 | fs.readFile('data.txt', 'utf-8', callback); 1302 | } 1303 | }, callback); 1304 | ``` 1305 | 1306 | __Arguments__ 1307 | 1308 | * `tasks` - An object. Each of its properties is either a function or an array of 1309 | requirements, with the function itself the last item in the array. The object's key 1310 | of a property serves as the name of the task defined by that property, 1311 | i.e. can be used when specifying requirements for other tasks. 1312 | The function receives two arguments: (1) a `callback(err, result)` which must be 1313 | called when finished, passing an `error` (which can be `null`) and the result of 1314 | the function's execution, and (2) a `results` object, containing the results of 1315 | the previously executed functions. 1316 | * `callback(err, results)` - A callback which is called when all the 1317 | tasks have been completed. It receives the `err` argument if any `tasks` 1318 | pass an error to their callback. Results are always returned; however, if 1319 | an error occurs, no further `tasks` will be performed, and the results 1320 | object will only contain partial results. Optional. 1321 | 1322 | __Example__ 1323 | 1324 | ```javascript 1325 | Insync.auto({ 1326 | get_data: function (callback) { 1327 | 1328 | console.log('in get_data'); 1329 | // Asynchronous code to get some data 1330 | callback(null, 'data', 'converted to array'); 1331 | }, 1332 | make_folder: function (callback) { 1333 | 1334 | console.log('in make_folder'); 1335 | // Asynchronous code to create a directory to store a file in 1336 | // this is run at the same time as getting the data 1337 | callback(null, 'folder'); 1338 | }, 1339 | write_file: ['get_data', 'make_folder', function (callback, results) { 1340 | 1341 | console.log('in write_file', JSON.stringify(results)); 1342 | // Once there is some data and the directory exists, 1343 | // write the data to a file in the directory 1344 | callback(null, 'filename'); 1345 | }], 1346 | email_link: ['write_file', function (callback, results) { 1347 | 1348 | console.log('in email_link', JSON.stringify(results)); 1349 | // Once the file is written let's email a link to it... 1350 | // results.write_file contains the filename returned by write_file. 1351 | callback(null, {'file':results.write_file, 'email':'user@example.com'}); 1352 | }] 1353 | }, function (err, results) { 1354 | 1355 | console.log('err = ', err); 1356 | console.log('results = ', results); 1357 | }); 1358 | ``` 1359 | 1360 | This is a fairly trivial example, but to do this using the basic parallel and series functions would look like this: 1361 | 1362 | ```javascript 1363 | Insync.parallel([ 1364 | function (callback) { 1365 | 1366 | console.log('in get_data'); 1367 | // Asynchronous code to get some data 1368 | callback(null, 'data', 'converted to array'); 1369 | }, 1370 | function (callback) { 1371 | 1372 | console.log('in make_folder'); 1373 | // Asynchronous code to create a directory to store a file in 1374 | // this is run at the same time as getting the data 1375 | callback(null, 'folder'); 1376 | } 1377 | ], 1378 | function (err, results) { 1379 | Insync.series([ 1380 | function (callback) { 1381 | 1382 | console.log('in write_file', JSON.stringify(results)); 1383 | // Once there is some data and the directory exists, 1384 | // write the data to a file in the directory 1385 | results.push('filename'); 1386 | callback(null); 1387 | }, 1388 | function (callback) { 1389 | 1390 | console.log('in email_link', JSON.stringify(results)); 1391 | // Once the file is written let's email a link to it... 1392 | callback(null, { file: results.pop(), email: 'user@example.com' }); 1393 | } 1394 | ]); 1395 | }); 1396 | ``` 1397 | 1398 | For a complicated series of asynchronous tasks, using the [`auto`](#auto) function makes adding new tasks much easier (and the code more readable). 1399 | 1400 | --------------------------------------- 1401 | 1402 | 1403 | ### retry([times,] task[, callback]) 1404 | 1405 | Attempts to get a successful response from `task` no more than `times` times before returning an error. If the task is successful, the `callback` will be passed the result of the successful task. If all attempts fail, the callback will be passed the error and result (if any) of the final attempt. 1406 | 1407 | __Arguments__ 1408 | 1409 | * `times` - An integer or an object. If `times` is a number, it indicates the maximum number of times `task` will be run. If `times` is an object, the number of tries is indicated by the `times` property. If `times` is an object, the `interval` property can be used to specify the amount of time to wait between attempts. Optional. Defaults to `{ times: 5, interval: 0 }`. 1410 | * `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)` 1411 | which must be called when finished, passing `err` (which can be `null`) and the `result` of 1412 | the function's execution, and (2) a `results` object, containing the results of 1413 | the previously executed functions (if nested inside another control flow). 1414 | * `callback(err, results)` - A callback which is called when the 1415 | task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`. Optional. 1416 | 1417 | The [`retry`](#retry) function can be used as a stand-alone control flow by passing a callback, as shown below: 1418 | 1419 | ```javascript 1420 | Insync.retry({ times: 3, interval: 200 }, apiMethod, function (err, result) { 1421 | 1422 | // Do something with the result 1423 | }); 1424 | ``` 1425 | 1426 | It can also be embeded within other control flow functions to retry individual methods that are not as reliable, like this: 1427 | 1428 | ```javascript 1429 | Insync.auto({ 1430 | users: api.getUsers.bind(api), 1431 | payments: Insync.retry(3, api.getPayments.bind(api)) 1432 | }, function (err, results) { 1433 | 1434 | // Do something with the results 1435 | }); 1436 | ``` 1437 | 1438 | --------------------------------------- 1439 | 1440 | 1441 | ### iterator(tasks) 1442 | 1443 | 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" at the next iterator with `iterator.next()`. 1444 | 1445 | This function is used internally by the insync module, but can be useful when you want to manually control the flow of functions in series. 1446 | 1447 | __Arguments__ 1448 | 1449 | * `tasks` - An array of functions to run. 1450 | 1451 | __Example__ 1452 | 1453 | ```javascript 1454 | var iterator = Insync.iterator([ 1455 | function () { console.log('one'); }, 1456 | function () { console.log('two'); }, 1457 | function () { console.log('three'); } 1458 | ]); 1459 | 1460 | node> var iterator2 = iterator(); 1461 | 'one' 1462 | node> var iterator3 = iterator2(); 1463 | 'two' 1464 | node> iterator3(); 1465 | 'three' 1466 | node> var nextfn = iterator2.next(); 1467 | node> nextfn(); 1468 | 'three' 1469 | ``` 1470 | 1471 | --------------------------------------- 1472 | 1473 | 1474 | ### apply(function, ...arguments) 1475 | 1476 | Creates a continuation function with some arguments already applied. 1477 | 1478 | 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. 1479 | 1480 | __Arguments__ 1481 | 1482 | * `function` - The function you want to eventually apply all arguments to. 1483 | * `...arguments` - Any number of arguments to automatically apply when the 1484 | continuation is called. 1485 | 1486 | __Example__ 1487 | 1488 | ```javascript 1489 | // Using apply 1490 | 1491 | Insync.parallel([ 1492 | Insync.apply(fs.writeFile, 'testfile1', 'test1'), 1493 | Insync.apply(fs.writeFile, 'testfile2', 'test2'), 1494 | ]); 1495 | 1496 | 1497 | // The same process without using apply 1498 | 1499 | Insync.parallel([ 1500 | function (callback) { 1501 | 1502 | fs.writeFile('testfile1', 'test1', callback); 1503 | }, 1504 | function (callback) { 1505 | 1506 | fs.writeFile('testfile2', 'test2', callback); 1507 | } 1508 | ]); 1509 | ``` 1510 | 1511 | It's possible to pass any number of additional arguments when calling the continuation: 1512 | 1513 | ```javascript 1514 | node> var fn = Insync.apply(console.log, 'one'); 1515 | node> fn('two', 'three'); 1516 | one 1517 | two 1518 | three 1519 | ``` 1520 | 1521 | --------------------------------------- 1522 | 1523 | 1524 | ### times(n, iterator[, callback]) 1525 | 1526 | Calls the `iterator` function `n` times, and accumulates results in the same manner you would use with [`map`](#map). 1527 | 1528 | __Arguments__ 1529 | 1530 | * `n` - The number of times to run the function. 1531 | * `iterator` - The function to call `n` times. 1532 | * `callback` - See [`map`](#map). Optional. 1533 | 1534 | __Example__ 1535 | 1536 | ```javascript 1537 | // Pretend this is some complicated asynchronous factory 1538 | var createUser = function (id, callback) { 1539 | 1540 | callback(null, { 1541 | id: 'user' + id 1542 | }); 1543 | }; 1544 | 1545 | // Generate 5 users 1546 | Insync.times(5, function (n, next) { 1547 | 1548 | createUser(n, function (err, user) { 1549 | 1550 | next(err, user); 1551 | }); 1552 | }, function (err, users) { 1553 | 1554 | // We should now have 5 users 1555 | }); 1556 | ``` 1557 | 1558 | 1559 | ### timesSeries(n, iterator[, callback]) 1560 | 1561 | The same as [`times`](#times), only the iterator is applied 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. 1562 | 1563 | 1564 | ### timesLimit(n, limit, iterator[, callback]) 1565 | 1566 | The same as [`times`](#times), except a maximum of `limit` iterators are run at a given time. 1567 | 1568 | ## Utils 1569 | 1570 | 1571 | ### ensureAsync(fn) 1572 | 1573 | Wrap a synchronous function to ensure that it executes asynchronously. If the function is already asynchronous, no extra deferral is added. This is useful for preventing stack overflows (`RangeError: Maximum call stack size exceeded`). 1574 | 1575 | __Arguments__ 1576 | 1577 | * `fn` - The function to make asynchronous. 1578 | 1579 | __Example__ 1580 | 1581 | ```javascript 1582 | // This runs the risk of stack overflows. 1583 | var sometimesAsync = function (arg, callback) { 1584 | 1585 | if (cache[arg]) { 1586 | return callback(null, cache[arg]); // This would be synchronous. 1587 | } 1588 | else { 1589 | doSomeIO(arg, callback); // This would be asynchronous. 1590 | } 1591 | }; 1592 | 1593 | // Defer sometimesAsync()'s callback if necessary. 1594 | Insync.mapSeries(args, Insync.ensureAsync(sometimesAsync), done); 1595 | ``` 1596 | 1597 | 1598 | ### memoize(fn[, hasher]) 1599 | 1600 | Caches the results of an asynchronous 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. 1601 | 1602 | If no hash function is specified, the first argument is used as a hash key, which may work reasonably if it is a string or a data type that converts to a distinct string. Note that objects and arrays will not behave reasonably. Neither will cases where the other arguments are significant. In such cases, specify your own hash function. 1603 | 1604 | The cache of results is exposed as the `memo` property of the function returned by `memoize`. 1605 | 1606 | __Arguments__ 1607 | 1608 | * `fn` - The function to proxy and cache results from. 1609 | * `hasher` - A function for generating a custom hash for storing 1610 | results. It has all the arguments applied to it apart from the callback, and 1611 | must be synchronous. Optional. 1612 | 1613 | __Example__ 1614 | 1615 | ```javascript 1616 | var slow_fn = function (name, callback) { 1617 | 1618 | // Do something 1619 | callback(null, result); 1620 | }; 1621 | var fn = Insync.memoize(slow_fn); 1622 | 1623 | // fn can now be used as if it were slow_fn 1624 | fn('some name', function () { 1625 | 1626 | // Callback 1627 | }); 1628 | ``` 1629 | 1630 | 1631 | ### unmemoize(fn) 1632 | 1633 | Undoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized form. Handy for testing. 1634 | 1635 | __Arguments__ 1636 | 1637 | * `fn` - the memoized function 1638 | 1639 | 1640 | ### log(function[, ...arguments]) 1641 | 1642 | Logs the result of an asynchronous function to the `console`. If multiple arguments are returned from the asynchronous function, `console.log` is called on each argument in order. 1643 | 1644 | __Arguments__ 1645 | 1646 | * `function` - The function you want to eventually apply all arguments to. 1647 | * `...arguments` - Any number of arguments to apply to the function. Optional. 1648 | 1649 | __Example__ 1650 | 1651 | ```javascript 1652 | var hello = function (name, callback) { 1653 | 1654 | setTimeout(function () { 1655 | 1656 | callback(null, 'hello ' + name); 1657 | }, 1000); 1658 | }; 1659 | ``` 1660 | 1661 | ```javascript 1662 | node> Insync.log(hello, 'world'); 1663 | 'hello world' 1664 | ``` 1665 | 1666 | --------------------------------------- 1667 | 1668 | 1669 | ### dir(function[, ...arguments]) 1670 | 1671 | Logs the result of an asynchronous function to the `console` using `console.dir` to display the properties of the resulting object. If multiple arguments are returned from the asynchronous function, `console.dir` is called on each argument in order. 1672 | 1673 | __Arguments__ 1674 | 1675 | * `function` - The function you want to eventually apply all arguments to. 1676 | * `...arguments` - Any number of arguments to apply to the function. Optional. 1677 | 1678 | __Example__ 1679 | 1680 | ```javascript 1681 | var hello = function (name, callback) { 1682 | 1683 | setTimeout(function () { 1684 | 1685 | callback(null, { hello: name }); 1686 | }, 1000); 1687 | }; 1688 | ``` 1689 | 1690 | ```javascript 1691 | node> Insync.dir(hello, 'world'); 1692 | {hello: 'world'} 1693 | ``` 1694 | -------------------------------------------------------------------------------- /images/insync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/continuationlabs/insync/af4d3ba3eed3ab4cb7474afd93f53d52b055b2d5/images/insync.png -------------------------------------------------------------------------------- /lib/collection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | const Util = require('./util'); 6 | 7 | 8 | // Declare internals 9 | 10 | const internals = {}; 11 | 12 | 13 | internals.doParallelLimit = function (limit, fn) { 14 | 15 | return function (...args) { 16 | 17 | return fn.apply(null, [internals._eachLimit(limit), ...args]); 18 | }; 19 | }; 20 | 21 | 22 | internals._map = function (arr) { 23 | 24 | const result = []; 25 | 26 | for (let i = 0, il = arr.length; i < il; ++i) { 27 | result.push({ 28 | index: i, 29 | value: arr[i] 30 | }); 31 | } 32 | 33 | return result; 34 | }; 35 | 36 | 37 | internals._unmap = function (arr) { 38 | 39 | const result = []; 40 | 41 | for (let i = 0, il = arr.length; i < il; ++i) { 42 | result.push(arr[i].value); 43 | } 44 | 45 | return result; 46 | }; 47 | 48 | 49 | internals._asyncMap = function (eachfn, arr, iterator, callback) { 50 | 51 | arr = internals._map(arr); 52 | 53 | if (typeof callback !== 'function') { 54 | eachfn(arr, function (item, callback) { 55 | 56 | iterator(item.value, function (err) { 57 | 58 | callback(err); 59 | }); 60 | }); 61 | } 62 | else { 63 | const results = []; 64 | 65 | eachfn(arr, function (item, callback) { 66 | 67 | iterator(item.value, function (err, value) { 68 | 69 | results[item.index] = value; 70 | callback(err); 71 | }); 72 | }, function (err) { 73 | 74 | callback(err, results); 75 | }); 76 | } 77 | }; 78 | 79 | 80 | internals._filter = function (eachfn, arr, iterator, callback) { 81 | 82 | let results = []; 83 | 84 | arr = internals._map(arr); 85 | 86 | eachfn(arr, function (item, callback) { 87 | 88 | iterator(item.value, function (err, value) { 89 | 90 | if (err) { 91 | return callback(err); 92 | } 93 | else if (value) { 94 | results.push(item); 95 | } 96 | 97 | callback(); 98 | }); 99 | }, function (err) { 100 | 101 | if (err) { 102 | return callback(err); 103 | } 104 | 105 | results.sort(function (a, b) { 106 | 107 | return a.index - b.index; 108 | }); 109 | 110 | results = internals._unmap(results); 111 | callback(null, results); 112 | }); 113 | }; 114 | 115 | 116 | internals._reject = function (eachfn, arr, iterator, callback) { 117 | 118 | let results = []; 119 | 120 | arr = internals._map(arr); 121 | 122 | eachfn(arr, function (item, callback) { 123 | 124 | iterator(item.value, function (err, value) { 125 | 126 | if (err) { 127 | return callback(err); 128 | } 129 | else if (!value) { 130 | results.push(item); 131 | } 132 | 133 | callback(); 134 | }); 135 | }, function (err) { 136 | 137 | if (err) { 138 | return callback(err); 139 | } 140 | 141 | results.sort(function (a, b) { 142 | 143 | return a.index - b.index; 144 | }); 145 | 146 | results = internals._unmap(results); 147 | callback(null, results); 148 | }); 149 | }; 150 | 151 | 152 | internals._detect = function (eachfn, arr, iterator, callback) { 153 | 154 | eachfn(arr, function (item, cb) { 155 | 156 | iterator(item, function (err, result) { 157 | 158 | if (err) { 159 | callback(err); 160 | callback = Util.noop; 161 | } 162 | else if (result) { 163 | callback(null, item); 164 | callback = Util.noop; 165 | } 166 | else { 167 | cb(); 168 | } 169 | }); 170 | }, callback); 171 | }; 172 | 173 | 174 | internals._concat = function (eachfn, arr, fn, callback) { 175 | 176 | let r = []; 177 | 178 | eachfn(arr, function (x, cb) { 179 | 180 | fn(x, function (err, y) { 181 | 182 | r = r.concat(y || []); 183 | cb(err); 184 | }); 185 | }, function (err) { 186 | 187 | callback(err, r); 188 | }); 189 | }; 190 | 191 | 192 | // Used in flow.js 193 | module.exports.doSeries = internals.doSeries = function (fn) { 194 | 195 | return function (...args) { 196 | 197 | return fn.apply(null, [module.exports.eachSeries, ...args]); 198 | }; 199 | }; 200 | 201 | 202 | // Used in flow.js 203 | module.exports.doParallel = internals.doParallel = function (fn) { 204 | 205 | return function (...args) { 206 | 207 | return fn.apply(null, [module.exports.each, ...args]); 208 | }; 209 | }; 210 | 211 | 212 | // Used in flow.js 213 | module.exports._eachLimit = internals._eachLimit = function (limit) { 214 | 215 | return function (arr, iterator, callback) { 216 | 217 | callback = callback || Util.noop; 218 | 219 | if (!arr.length || limit <= 0) { 220 | return callback(); 221 | } 222 | 223 | let completed = 0; 224 | let started = 0; 225 | let running = 0; 226 | let errored = false; 227 | 228 | (function replenish () { 229 | 230 | const iteratorCallback = function (err) { 231 | 232 | if (err) { 233 | callback(err); 234 | callback = Util.noop; 235 | errored = true; 236 | } 237 | else { 238 | completed++; 239 | running--; 240 | 241 | if (completed >= arr.length) { 242 | callback(); 243 | } 244 | else { 245 | replenish(); 246 | } 247 | } 248 | }; 249 | 250 | while (running < limit && started < arr.length && errored === false) { 251 | started++; 252 | running++; 253 | 254 | iterator(arr[started - 1], iteratorCallback); 255 | } 256 | })(); 257 | }; 258 | }; 259 | 260 | 261 | internals._eachOfLimit = function (limit) { 262 | 263 | return function (object, iterator, callback) { 264 | 265 | callback = callback || Util.noop; 266 | 267 | const isArrayLike = Util.isArrayLike(object); 268 | let size; 269 | let keys; 270 | 271 | if (isArrayLike) { 272 | size = object.length; 273 | } 274 | else { 275 | keys = Object.keys(object); 276 | size = keys.length; 277 | } 278 | 279 | if (!size || limit <= 0) { 280 | return callback(); 281 | } 282 | 283 | let completed = 0; 284 | let started = 0; 285 | let running = 0; 286 | let errored = false; 287 | 288 | (function replenish () { 289 | 290 | const iteratorCallback = function (err) { 291 | 292 | if (err) { 293 | callback(err); 294 | callback = Util.noop; 295 | errored = true; 296 | } 297 | else { 298 | completed++; 299 | running--; 300 | 301 | if (completed >= size) { 302 | callback(); 303 | } 304 | else { 305 | replenish(); 306 | } 307 | } 308 | }; 309 | 310 | while (running < limit && started < size && errored === false) { 311 | let key = isArrayLike ? started : keys[started]; 312 | started++; 313 | running++; 314 | iterator(object[key], key, iteratorCallback); 315 | } 316 | })(); 317 | }; 318 | }; 319 | 320 | 321 | // Used in flow.js 322 | module.exports._mapLimit = internals._mapLimit = function (limit) { 323 | 324 | return internals.doParallelLimit(limit, internals._asyncMap); 325 | }; 326 | 327 | 328 | module.exports.each = function (arr, iterator, callback) { 329 | 330 | callback = callback || Util.noop; 331 | 332 | if (!arr.length) { 333 | return callback(); 334 | } 335 | 336 | let completed = 0; 337 | 338 | const done = function (err) { 339 | 340 | if (err) { 341 | callback(err); 342 | callback = Util.noop; 343 | } 344 | else { 345 | ++completed; 346 | 347 | if (completed >= arr.length) { 348 | callback(); 349 | } 350 | } 351 | }; 352 | 353 | for (let i = 0, il = arr.length; i < il; ++i) { 354 | iterator(arr[i], Util.onlyOnce(done)); 355 | } 356 | }; 357 | 358 | 359 | module.exports.eachSeries = function (arr, iterator, callback) { 360 | 361 | callback = callback || Util.noop; 362 | 363 | if (!arr.length) { 364 | return callback(); 365 | } 366 | 367 | let completed = 0; 368 | 369 | const iterate = function () { 370 | 371 | iterator(arr[completed], function (err) { 372 | 373 | if (err) { 374 | callback(err); 375 | callback = Util.noop; 376 | } 377 | else { 378 | completed++; 379 | 380 | if (completed >= arr.length) { 381 | callback(); 382 | } 383 | else { 384 | iterate(); 385 | } 386 | } 387 | }); 388 | }; 389 | 390 | iterate(); 391 | }; 392 | 393 | 394 | module.exports.eachLimit = function (arr, limit, iterator, callback) { 395 | 396 | const fn = internals._eachLimit(limit); 397 | 398 | fn.apply(null, [arr, iterator, callback]); 399 | }; 400 | 401 | 402 | module.exports.eachOf = function (object, iterator, callback) { 403 | 404 | const isArrayLike = Util.isArrayLike(object); 405 | let size; 406 | let keys; 407 | 408 | if (isArrayLike) { 409 | size = object.length; 410 | } 411 | else { 412 | keys = Object.keys(object); 413 | size = keys.length; 414 | } 415 | 416 | callback = callback || Util.noop; 417 | 418 | if (!size) { 419 | return callback(); 420 | } 421 | 422 | let completed = 0; 423 | 424 | const done = function (err) { 425 | 426 | if (err) { 427 | callback(err); 428 | callback = Util.noop; 429 | } 430 | else { 431 | ++completed; 432 | 433 | if (completed >= size) { 434 | callback(); 435 | } 436 | } 437 | }; 438 | 439 | if (isArrayLike) { 440 | for (let i = 0; i < size; ++i) { 441 | iterator(object[i], i, Util.onlyOnce(done)); 442 | } 443 | } 444 | else { 445 | for (let i = 0; i < size; ++i) { 446 | let key = keys[i]; 447 | iterator(object[key], key, Util.onlyOnce(done)); 448 | } 449 | } 450 | }; 451 | 452 | 453 | module.exports.eachOfSeries = function (object, iterator, callback) { 454 | 455 | const isArrayLike = Util.isArrayLike(object); 456 | let size; 457 | let keys; 458 | 459 | if (isArrayLike) { 460 | size = object.length; 461 | } 462 | else { 463 | keys = Object.keys(object); 464 | size = keys.length; 465 | } 466 | 467 | callback = callback || Util.noop; 468 | 469 | if (!size) { 470 | return callback(); 471 | } 472 | 473 | let completed = 0; 474 | 475 | const complete = function (err) { 476 | 477 | if (err) { 478 | callback(err); 479 | callback = Util.noop; 480 | } 481 | else { 482 | completed++; 483 | 484 | if (completed >= size) { 485 | callback(); 486 | } 487 | else { 488 | iterate(); 489 | } 490 | } 491 | }; 492 | 493 | const iterate = isArrayLike ? 494 | function () { iterator(object[completed], completed, complete); } : 495 | function () { let key = keys[completed]; iterator(object[key], key, complete); }; 496 | 497 | iterate(); 498 | }; 499 | 500 | 501 | module.exports.eachOfLimit = function (arr, limit, iterator, callback) { 502 | 503 | let fn = internals._eachOfLimit(limit); 504 | 505 | fn.apply(null, [arr, iterator, callback]); 506 | }; 507 | 508 | 509 | module.exports.map = internals.doParallel(internals._asyncMap); 510 | 511 | 512 | module.exports.mapSeries = internals.doSeries(internals._asyncMap); 513 | 514 | 515 | module.exports.mapLimit = function (arr, limit, iterator, callback) { 516 | 517 | return internals._mapLimit(limit)(arr, iterator, callback); 518 | }; 519 | 520 | 521 | module.exports.filter = internals.doParallel(internals._filter); 522 | 523 | 524 | module.exports.filterSeries = internals.doSeries(internals._filter); 525 | 526 | 527 | module.exports.reject = internals.doParallel(internals._reject); 528 | 529 | 530 | module.exports.rejectSeries = internals.doSeries(internals._reject); 531 | 532 | 533 | // reduce() only has a series version. 534 | // A parallel reduce() won't work in many situations. 535 | module.exports.reduce = function (arr, memo, iterator, callback) { 536 | 537 | module.exports.eachSeries(arr, function (item, callback) { 538 | 539 | iterator(memo, item, function (err, value) { 540 | 541 | memo = value; 542 | callback(err); 543 | }); 544 | }, function (err) { 545 | 546 | callback(err, memo); 547 | }); 548 | }; 549 | 550 | 551 | module.exports.reduceRight = function (arr, memo, iterator, callback) { 552 | 553 | const reversed = []; 554 | 555 | for (let i = arr.length - 1; i >= 0; --i) { 556 | reversed.push(arr[i]); 557 | } 558 | 559 | module.exports.reduce(reversed, memo, iterator, callback); 560 | }; 561 | 562 | 563 | module.exports.detect = internals.doParallel(internals._detect); 564 | 565 | 566 | module.exports.detectSeries = internals.doSeries(internals._detect); 567 | 568 | 569 | module.exports.sortBy = function (arr, iterator, callback) { 570 | 571 | module.exports.map(arr, function (item, callback) { 572 | 573 | iterator(item, function (err, criteria) { 574 | 575 | if (err) { 576 | return callback(err); 577 | } 578 | 579 | callback(null, { 580 | value: item, 581 | criteria: criteria 582 | }); 583 | }); 584 | }, function (err, results) { 585 | 586 | if (err) { 587 | return callback(err); 588 | } 589 | 590 | results.sort(function (left, right) { 591 | 592 | const a = left.criteria; 593 | const b = right.criteria; 594 | 595 | return a < b ? -1 : a > b ? 1 : 0; 596 | }); 597 | 598 | results = internals._unmap(results); 599 | callback(null, results); 600 | }); 601 | }; 602 | 603 | 604 | module.exports.some = function (arr, iterator, callback) { 605 | 606 | module.exports.each(arr, function (item, cb) { 607 | 608 | iterator(item, function (err, value) { 609 | 610 | if (err) { 611 | callback(err); 612 | callback = Util.noop; 613 | } 614 | else if (value) { 615 | callback(null, true); 616 | callback = Util.noop; 617 | } 618 | 619 | cb(); 620 | }); 621 | }, function (err) { 622 | 623 | callback(err, false); 624 | }); 625 | }; 626 | 627 | 628 | module.exports.every = function (arr, iterator, callback) { 629 | 630 | module.exports.each(arr, function (item, cb) { 631 | 632 | iterator(item, function (err, value) { 633 | 634 | if (err) { 635 | callback(err); 636 | callback = Util.noop; 637 | } 638 | else if (!value) { 639 | callback(null, false); 640 | callback = Util.noop; 641 | } 642 | 643 | cb(); 644 | }); 645 | }, function (err) { 646 | 647 | callback(err, true); 648 | }); 649 | }; 650 | 651 | 652 | module.exports.concat = internals.doParallel(internals._concat); 653 | 654 | 655 | module.exports.concatSeries = internals.doSeries(internals._concat); 656 | -------------------------------------------------------------------------------- /lib/flow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | const Collection = require('./collection'); 6 | const Util = require('./util'); 7 | 8 | 9 | // Declare internals 10 | 11 | const internals = { 12 | DEFAULT_RETRIES: 5, 13 | DEFAULT_INTERVAL: 0 14 | }; 15 | 16 | 17 | internals._applyEach = function (eachfn, fns, ...args) { 18 | 19 | const go = function (...args) { 20 | 21 | const self = this; 22 | const callback = args.pop(); 23 | 24 | return eachfn(fns, function (fn, cb) { 25 | 26 | fn.apply(self, args.concat([cb])); 27 | }, callback); 28 | }; 29 | 30 | if (args.length > 0) { 31 | return go.apply(this, args); 32 | } 33 | 34 | return go; 35 | }; 36 | 37 | 38 | internals._parallel = function (eachfn, tasks, callback) { 39 | 40 | callback = callback || Util.noop; 41 | 42 | if (Array.isArray(tasks)) { 43 | eachfn.map(tasks, function (fn, callback) { 44 | 45 | fn(function (err, ...args) { 46 | 47 | if (args.length <= 1) { 48 | args = args[0]; 49 | } 50 | 51 | callback.call(null, err, args); 52 | }); 53 | }, callback); 54 | } 55 | else { 56 | const results = {}; 57 | 58 | eachfn.each(Object.keys(tasks), function (k, callback) { 59 | 60 | tasks[k](function (err, ...args) { 61 | 62 | if (args.length <= 1) { 63 | args = args[0]; 64 | } 65 | 66 | results[k] = args; 67 | callback(err); 68 | }); 69 | }, function (err) { 70 | 71 | callback(err, results); 72 | }); 73 | } 74 | }; 75 | 76 | 77 | module.exports.series = function (tasks, callback) { 78 | 79 | callback = callback || Util.noop; 80 | 81 | if (Array.isArray(tasks)) { 82 | Collection.mapSeries(tasks, function (fn, callback) { 83 | 84 | fn(function (err, ...args) { 85 | 86 | if (args.length <= 1) { 87 | args = args[0]; 88 | } 89 | 90 | callback.call(null, err, args); 91 | }); 92 | }, callback); 93 | } 94 | else { 95 | const results = {}; 96 | 97 | Collection.eachSeries(Object.keys(tasks), function (k, callback) { 98 | 99 | tasks[k](function (err, ...args) { 100 | 101 | if (args.length <= 1) { 102 | args = args[0]; 103 | } 104 | 105 | results[k] = args; 106 | callback(err); 107 | }); 108 | }, function (err) { 109 | 110 | callback(err, results); 111 | }); 112 | } 113 | }; 114 | 115 | 116 | module.exports.parallel = function (tasks, callback) { 117 | 118 | internals._parallel({ 119 | map: Collection.map, 120 | each: Collection.each 121 | }, tasks, callback); 122 | }; 123 | 124 | 125 | module.exports.parallelLimit = function (tasks, limit, callback) { 126 | 127 | internals._parallel({ 128 | map: Collection._mapLimit(limit), 129 | each: Collection._eachLimit(limit) 130 | }, tasks, callback); 131 | }; 132 | 133 | 134 | module.exports.whilst = function (test, iterator, callback) { 135 | 136 | if (test()) { 137 | iterator(function (err) { 138 | 139 | if (err) { 140 | return callback(err); 141 | } 142 | 143 | module.exports.whilst(test, iterator, callback); 144 | }); 145 | } 146 | else { 147 | callback(); 148 | } 149 | }; 150 | 151 | 152 | module.exports.doWhilst = function (iterator, test, callback) { 153 | 154 | iterator(function (err, ...args) { 155 | 156 | if (err) { 157 | return callback(err); 158 | } 159 | 160 | if (test.apply(null, args)) { 161 | module.exports.doWhilst(iterator, test, callback); 162 | } 163 | else { 164 | callback(); 165 | } 166 | }); 167 | }; 168 | 169 | 170 | module.exports.until = function (test, iterator, callback) { 171 | 172 | if (!test()) { 173 | iterator(function (err) { 174 | 175 | if (err) { 176 | return callback(err); 177 | } 178 | 179 | module.exports.until(test, iterator, callback); 180 | }); 181 | } 182 | else { 183 | callback(); 184 | } 185 | }; 186 | 187 | 188 | module.exports.doUntil = function (iterator, test, callback) { 189 | 190 | iterator(function (err, ...args) { 191 | 192 | if (err) { 193 | return callback(err); 194 | } 195 | 196 | if (!test.apply(null, args)) { 197 | module.exports.doUntil(iterator, test, callback); 198 | } 199 | else { 200 | callback(); 201 | } 202 | }); 203 | }; 204 | 205 | 206 | module.exports.forever = function (fn, callback) { 207 | 208 | const next = function (err) { 209 | 210 | if (err) { 211 | if (callback) { 212 | return callback(err); 213 | } 214 | 215 | throw err; 216 | } 217 | 218 | fn(next); 219 | }; 220 | 221 | next(); 222 | }; 223 | 224 | 225 | module.exports.waterfall = function (tasks, callback) { 226 | 227 | callback = callback || Util.noop; 228 | 229 | if (!Array.isArray(tasks)) { 230 | const err = new Error('First argument to waterfall must be an array of functions'); 231 | 232 | return callback(err); 233 | } 234 | 235 | if (!tasks.length) { 236 | return callback(); 237 | } 238 | 239 | const size = tasks.length; 240 | let called = false; 241 | let args = []; 242 | 243 | const iterate = function (completed) { 244 | 245 | const func = tasks[completed]; 246 | 247 | const done = function (err) { 248 | 249 | if (called) { 250 | throw new Error('Callback was already called'); 251 | } 252 | 253 | called = true; 254 | 255 | if (err) { 256 | return callback(...arguments); 257 | } 258 | 259 | ++completed; 260 | if (completed === size) { 261 | return callback(...arguments); 262 | } 263 | 264 | const l = arguments.length; 265 | args = Array(l > 1 ? l - 1 : 0); 266 | 267 | for (let i = 1; i < l; ++i) { 268 | args[i - 1] = arguments[i]; 269 | } 270 | 271 | iterate(completed); 272 | }; 273 | 274 | called = false; 275 | args.push(done); 276 | func.apply(null, args); 277 | }; 278 | 279 | iterate(0); 280 | }; 281 | 282 | 283 | module.exports.compose = function (...args) { 284 | 285 | return module.exports.seq.apply(null, Array.prototype.reverse.call(args)); 286 | }; 287 | 288 | 289 | module.exports.seq = function (...fns) { 290 | 291 | return function (...args) { 292 | 293 | const self = this; 294 | const callback = args.pop(); 295 | 296 | Collection.reduce(fns, args, function (newargs, fn, cb) { 297 | 298 | fn.apply(self, newargs.concat([function (err, ...nextargs) { 299 | 300 | cb(err, nextargs); 301 | }])); 302 | }, function (err, results) { 303 | 304 | callback.apply(self, [err].concat(results)); 305 | }); 306 | }; 307 | }; 308 | 309 | 310 | module.exports.applyEach = Collection.doParallel(internals._applyEach); 311 | 312 | 313 | module.exports.applyEachSeries = Collection.doSeries(internals._applyEach); 314 | 315 | 316 | internals._queue = function (worker, concurrency, payload) { 317 | 318 | if (concurrency === undefined) { 319 | concurrency = 1; 320 | } 321 | else if (concurrency < 1 || concurrency >>> 0 !== concurrency) { 322 | throw new RangeError('Concurrency must be a positive integer'); 323 | } 324 | 325 | const _insert = function (q, data, pos, callback) { 326 | 327 | if (callback !== undefined && typeof callback !== 'function') { 328 | throw new TypeError('Callback must be a function'); 329 | } 330 | 331 | q.started = true; 332 | 333 | if (!Array.isArray(data)) { 334 | data = [data]; 335 | } 336 | 337 | if (data.length === 0 && q.idle()) { 338 | // Call drain immediately if there are no tasks 339 | return setImmediate(q.drain); 340 | } 341 | 342 | for (let i = 0, il = data.length; i < il; ++i) { 343 | let item = { 344 | data: data[i], 345 | callback: typeof callback === 'function' ? callback : Util.noop 346 | }; 347 | 348 | if (pos) { 349 | q.tasks.unshift(item); 350 | } else { 351 | q.tasks.push(item); 352 | } 353 | 354 | if (q.tasks.length === q.concurrency) { 355 | q.saturated(); 356 | } 357 | } 358 | 359 | setImmediate(q.process); 360 | }; 361 | 362 | const next = function (q, tasks) { 363 | 364 | return function (...args) { 365 | 366 | workers--; 367 | 368 | for (let i = 0, il = tasks.length; i < il; ++i) { 369 | const task = tasks[i]; 370 | task.callback.apply(task, args); 371 | } 372 | 373 | if (q.tasks.length + workers === 0) { 374 | q.drain(); 375 | } 376 | 377 | q.process(); 378 | }; 379 | }; 380 | 381 | let workers = 0; 382 | const q = { 383 | tasks: [], 384 | concurrency: concurrency, 385 | saturated: Util.noop, 386 | empty: Util.noop, 387 | drain: Util.noop, 388 | started: false, 389 | paused: false, 390 | push: function (data, callback) { 391 | 392 | _insert(q, data, false, callback); 393 | }, 394 | kill: function () { 395 | 396 | q.drain = Util.noop; 397 | q.tasks = []; 398 | }, 399 | unshift: function (data, callback) { 400 | 401 | _insert(q, data, true, callback); 402 | }, 403 | process: function () { 404 | 405 | while (!q.paused && workers < q.concurrency && q.tasks.length) { 406 | let tasks = payload ? q.tasks.splice(0, payload) : q.tasks.splice(0, q.tasks.length); 407 | const length = tasks.length; 408 | const data = new Array(length); 409 | 410 | for (let i = 0; i < length; ++i) { 411 | data[i] = tasks[i].data; 412 | } 413 | 414 | if (q.tasks.length === 0) { 415 | q.empty(); 416 | } 417 | 418 | const cb = Util.onlyOnce(next(q, tasks)); 419 | workers++; 420 | worker(data, cb); 421 | } 422 | }, 423 | length: function () { 424 | 425 | return q.tasks.length; 426 | }, 427 | running: function () { 428 | 429 | return workers; 430 | }, 431 | idle: function () { 432 | 433 | return q.tasks.length + workers === 0; 434 | }, 435 | pause: function () { 436 | 437 | q.paused = true; 438 | }, 439 | resume: function () { 440 | 441 | if (q.paused === false) { 442 | return; 443 | } 444 | 445 | q.paused = false; 446 | // Need to call q.process once per concurrent 447 | // worker to preserve full concurrency after pause 448 | const resumeCount = Math.min(q.concurrency, q.tasks.length); 449 | 450 | for (let w = 1; w <= resumeCount; ++w) { 451 | setImmediate(q.process); 452 | } 453 | } 454 | }; 455 | 456 | return q; 457 | }; 458 | 459 | 460 | module.exports.queue = function (worker, concurrency) { 461 | 462 | return internals._queue(function (items, cb) { 463 | 464 | worker(items[0], cb); 465 | }, concurrency, 1); 466 | }; 467 | 468 | 469 | module.exports.priorityQueue = function (worker, concurrency) { 470 | 471 | const _compareTasks = function (a, b) { 472 | 473 | return a.priority - b.priority; 474 | }; 475 | 476 | const _binarySearch = function (sequence, item, compare) { 477 | 478 | let beg = -1; 479 | let end = sequence.length - 1; 480 | 481 | while (beg < end) { 482 | let mid = beg + ((end - beg + 1) >>> 1); 483 | 484 | if (compare(item, sequence[mid]) >= 0) { 485 | beg = mid; 486 | } 487 | else { 488 | end = mid - 1; 489 | } 490 | } 491 | 492 | return beg; 493 | }; 494 | 495 | const _insert = function (q, data, priority, callback) { 496 | 497 | if (callback !== undefined && typeof callback !== 'function') { 498 | throw new TypeError('Callback must be a function'); 499 | } 500 | 501 | q.started = true; 502 | 503 | if (!Array.isArray(data)) { 504 | data = [data]; 505 | } 506 | 507 | if (data.length === 0) { 508 | // Call drain immediately if there are no tasks 509 | return setImmediate(q.drain); 510 | } 511 | 512 | for (let i = 0, il = data.length; i < il; ++i) { 513 | let task = data[i]; 514 | let item = { 515 | data: task, 516 | priority: priority, 517 | callback: typeof callback === 'function' ? callback : Util.noop 518 | }; 519 | 520 | q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); 521 | 522 | if (q.tasks.length === q.concurrency) { 523 | q.saturated(); 524 | } 525 | 526 | setImmediate(q.process); 527 | } 528 | }; 529 | 530 | // Start with a normal queue 531 | let q = module.exports.queue(worker, concurrency); 532 | 533 | // Override push to accept second parameter representing priority 534 | q.push = function (data, priority, callback) { 535 | 536 | _insert(q, data, priority, callback); 537 | }; 538 | 539 | // Remove unshift function 540 | delete q.unshift; 541 | 542 | return q; 543 | }; 544 | 545 | 546 | module.exports.cargo = function (worker, payload) { 547 | 548 | return internals._queue(worker, 1, payload); 549 | }; 550 | 551 | 552 | module.exports.auto = function (tasks, callback) { 553 | 554 | callback = callback || Util.noop; 555 | 556 | const keys = Object.keys(tasks); 557 | let remainingTasks = keys.length; 558 | 559 | if (!remainingTasks) { 560 | return callback(); 561 | } 562 | 563 | const results = {}; 564 | const listeners = []; 565 | 566 | const addListener = function (fn) { 567 | 568 | listeners.unshift(fn); 569 | }; 570 | 571 | const removeListener = function (fn) { 572 | 573 | const index = listeners.indexOf(fn); 574 | listeners.splice(index, 1); 575 | }; 576 | 577 | const taskComplete = function () { 578 | 579 | remainingTasks--; 580 | let copy = listeners.slice(0); 581 | for (let i = 0, il = copy.length; i < il; ++i) { 582 | let fn = copy[i]; 583 | fn(); 584 | } 585 | }; 586 | 587 | const taskCallbackGenerator = function (key) { 588 | 589 | return function taskCallback (err, ...args) { 590 | 591 | if (args.length <= 1) { 592 | args = args[0]; 593 | } 594 | 595 | if (err) { 596 | const safeResults = {}; 597 | const keys = Object.keys(results); 598 | 599 | for (let i = 0, il = keys.length; i < il; ++i) { 600 | let rkey = keys[i]; 601 | 602 | safeResults[rkey] = results[rkey]; 603 | } 604 | 605 | safeResults[key] = args; 606 | callback(err, safeResults); 607 | // Stop subsequent errors hitting callback multiple times 608 | callback = Util.noop; 609 | } 610 | else { 611 | results[key] = args; 612 | setImmediate(taskComplete); 613 | } 614 | }; 615 | }; 616 | 617 | const taskReadyGenerator = function (key, requires) { 618 | 619 | return function taskReady () { 620 | // Note: Originally this was ported with the 621 | // !results.hasOwnProperty(k) check first in the 622 | // return statement. However, coverage was never achieved 623 | // for this line as I have yet to see a case where this 624 | // was false. A test would be appreciated, otherwise this 625 | // return statement should be refactored. 626 | let run = true; 627 | let i = requires.length; 628 | 629 | while (i--) { 630 | run = results.hasOwnProperty(requires[i]); 631 | if (!run) { 632 | break; 633 | } 634 | } 635 | 636 | return run && !(key in results); 637 | }; 638 | }; 639 | 640 | const taskListenerGenerator = function (ready, task, taskCallback) { 641 | 642 | const listener = function taskListener () { 643 | 644 | if (ready()) { 645 | removeListener(listener); 646 | task[task.length - 1](taskCallback, results); 647 | } 648 | }; 649 | 650 | return listener; 651 | }; 652 | 653 | addListener(function () { 654 | 655 | if (!remainingTasks) { 656 | 657 | const theCallback = callback; 658 | 659 | // Prevent final callback from calling itself if it errors 660 | callback = Util.noop; 661 | theCallback(null, results); 662 | } 663 | }); 664 | 665 | for (let i = 0, il = keys.length; i < il; ++i) { 666 | 667 | const k = keys[i]; 668 | const task = Array.isArray(tasks[k]) ? tasks[k] : [tasks[k]]; 669 | const taskCallback = taskCallbackGenerator(k); 670 | const requires = task.slice(0, Math.abs(task.length - 1)); 671 | const ready = taskReadyGenerator(k, requires); 672 | const listener = taskListenerGenerator(ready, task, taskCallback); 673 | 674 | // Prevent deadlocks 675 | let len = requires.length; 676 | 677 | while (len--) { 678 | let dep = tasks[requires[len]]; 679 | 680 | if (!dep) { 681 | throw new Error('Has inexistant dependency'); 682 | } 683 | 684 | if (Array.isArray(dep) && dep.indexOf(k) !== -1) { 685 | throw new Error('Has cyclic dependencies'); 686 | } 687 | } 688 | 689 | if (ready()) { 690 | task[task.length - 1](taskCallback, results); 691 | } 692 | else { 693 | addListener(listener); 694 | } 695 | } 696 | }; 697 | 698 | 699 | module.exports.retry = function (times, task, callback) { 700 | 701 | const attempts = []; 702 | let interval; 703 | 704 | // Parse arguments 705 | if (typeof times === 'function') { 706 | // retry(task[, callback]) 707 | callback = task; 708 | task = times; 709 | times = internals.DEFAULT_RETRIES; 710 | interval = internals.DEFAULT_INTERVAL; 711 | } 712 | else if (typeof times === 'number') { 713 | // retry(number, task[, callback]) 714 | times = (times >>> 0) || internals.DEFAULT_RETRIES; 715 | interval = internals.DEFAULT_INTERVAL; 716 | } 717 | else if (times !== null && typeof times === 'object') { 718 | // retry(object, task[, callback]) 719 | interval = typeof times.interval === 'number' ? times.interval : internals.DEFAULT_INTERVAL; 720 | times = (times.times >>> 0) || internals.DEFAULT_RETRIES; 721 | } 722 | else { 723 | throw TypeError('Retry expects number or object'); 724 | } 725 | 726 | const wrappedTask = function (wrappedCallback, wrappedResults) { 727 | 728 | const retryAttempt = function (task, finalAttempt) { 729 | 730 | return function (seriesCallback) { 731 | 732 | task(function (err, result) { 733 | 734 | seriesCallback(!err || finalAttempt, { 735 | err: err, 736 | result: result 737 | }); 738 | }, wrappedResults); 739 | }; 740 | }; 741 | 742 | const retryTimeout = function (seriesCallback) { 743 | 744 | setTimeout(seriesCallback, interval); 745 | }; 746 | 747 | while (times) { 748 | let finalAttempt = --times === 0; 749 | attempts.push(retryAttempt(task, finalAttempt)); 750 | 751 | if (!finalAttempt && interval > 0) { 752 | attempts.push(retryTimeout); 753 | } 754 | } 755 | 756 | module.exports.series(attempts, function (done, data) { 757 | 758 | data = data[data.length - 1]; 759 | (wrappedCallback || callback)(data.err, data.result); 760 | }); 761 | }; 762 | 763 | // If a callback is passed, run this as a control flow 764 | return callback ? wrappedTask() : wrappedTask; 765 | }; 766 | 767 | 768 | module.exports.iterator = function (tasks) { 769 | 770 | const makeCallback = function (index) { 771 | 772 | const fn = function (...args) { 773 | 774 | if (tasks.length) { 775 | tasks[index].apply(null, args); 776 | } 777 | 778 | return fn.next(); 779 | }; 780 | 781 | fn.next = function () { 782 | 783 | return (index < tasks.length - 1) ? makeCallback(index + 1) : null; 784 | }; 785 | 786 | return fn; 787 | }; 788 | 789 | return makeCallback(0); 790 | }; 791 | 792 | 793 | module.exports.apply = function (fn, ...args) { 794 | 795 | return function (...innerArgs) { 796 | 797 | return fn.apply(null, args.concat(innerArgs)); 798 | }; 799 | }; 800 | 801 | 802 | module.exports.times = function (count, iterator, callback) { 803 | 804 | let counter = []; 805 | 806 | for (let i = 0; i < count; ++i) { 807 | counter.push(i); 808 | } 809 | 810 | return Collection.map(counter, iterator, callback); 811 | }; 812 | 813 | 814 | module.exports.timesSeries = function (count, iterator, callback) { 815 | 816 | let counter = []; 817 | 818 | for (let i = 0; i < count; ++i) { 819 | counter.push(i); 820 | } 821 | 822 | return Collection.mapSeries(counter, iterator, callback); 823 | }; 824 | 825 | 826 | module.exports.timesLimit = function (count, limit, iterator, callback) { 827 | 828 | let counter = []; 829 | 830 | for (let i = 0; i < count; ++i) { 831 | counter.push(i); 832 | } 833 | 834 | return Collection.mapLimit(counter, limit, iterator, callback); 835 | }; 836 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | const Collection = require('./collection'); 6 | const Flow = require('./flow'); 7 | const Util = require('./util'); 8 | 9 | 10 | // Declare internals 11 | 12 | const internals = {}; 13 | 14 | 15 | module.exports = { 16 | 17 | // Collections 18 | each: Collection.each, 19 | forEach: Collection.each, 20 | eachSeries: Collection.eachSeries, 21 | forEachSeries: Collection.eachSeries, 22 | eachLimit: Collection.eachLimit, 23 | forEachLimit: Collection.eachLimit, 24 | eachOf: Collection.eachOf, 25 | forEachOf: Collection.eachOf, 26 | eachOfSeries: Collection.eachOfSeries, 27 | forEachOfSeries: Collection.eachOfSeries, 28 | eachOfLimit: Collection.eachOfLimit, 29 | forEachOfLimit: Collection.eachOfLimit, 30 | map: Collection.map, 31 | mapSeries: Collection.mapSeries, 32 | mapLimit: Collection.mapLimit, 33 | filter: Collection.filter, 34 | filterSeries: Collection.filterSeries, 35 | select: Collection.filter, 36 | selectSeries: Collection.filterSeries, 37 | reject: Collection.reject, 38 | rejectSeries: Collection.rejectSeries, 39 | reduce: Collection.reduce, 40 | inject: Collection.reduce, 41 | foldl: Collection.reduce, 42 | reduceRight: Collection.reduceRight, 43 | foldr: Collection.reduceRight, 44 | detect: Collection.detect, 45 | detectSeries: Collection.detectSeries, 46 | sortBy: Collection.sortBy, 47 | some: Collection.some, 48 | any: Collection.some, 49 | every: Collection.every, 50 | all: Collection.every, 51 | concat: Collection.concat, 52 | concatSeries: Collection.concatSeries, 53 | 54 | // Control flow 55 | series: Flow.series, 56 | parallel: Flow.parallel, 57 | parallelLimit: Flow.parallelLimit, 58 | whilst: Flow.whilst, 59 | doWhilst: Flow.doWhilst, 60 | until: Flow.until, 61 | doUntil: Flow.doUntil, 62 | forever: Flow.forever, 63 | waterfall: Flow.waterfall, 64 | compose: Flow.compose, 65 | seq: Flow.seq, 66 | applyEach: Flow.applyEach, 67 | applyEachSeries: Flow.applyEachSeries, 68 | queue: Flow.queue, 69 | priorityQueue: Flow.priorityQueue, 70 | cargo: Flow.cargo, 71 | auto: Flow.auto, 72 | retry: Flow.retry, 73 | iterator: Flow.iterator, 74 | apply: Flow.apply, 75 | times: Flow.times, 76 | timesSeries: Flow.timesSeries, 77 | timesLimit: Flow.timesLimit, 78 | 79 | // Utils 80 | ensureAsync: Util.ensureAsync, 81 | memoize: Util.memoize, 82 | unmemoize: Util.unmemoize, 83 | log: Util.log, 84 | dir: Util.dir 85 | }; 86 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | 6 | // Declare internals 7 | 8 | const internals = {}; 9 | 10 | 11 | internals.consoleFn = function (name) { 12 | 13 | return function (fn, ...args) { 14 | 15 | fn.apply(null, args.concat([function (err) { 16 | 17 | if (err) { 18 | return console.error(err); 19 | } 20 | 21 | const consoleFn = console[name]; 22 | 23 | if (typeof consoleFn === 'function') { 24 | for (let i = 1, il = arguments.length; i < il; ++i) { 25 | consoleFn(arguments[i]); 26 | } 27 | } 28 | }])); 29 | }; 30 | }; 31 | 32 | 33 | module.exports.noop = function noop () {}; 34 | 35 | 36 | module.exports.onlyOnce = function (fn) { 37 | 38 | let called = false; 39 | 40 | return function (...args) { 41 | 42 | if (called) { 43 | throw new Error('Callback was already called.'); 44 | } 45 | 46 | called = true; 47 | fn.apply(global, args); 48 | }; 49 | }; 50 | 51 | 52 | module.exports.ensureAsync = function (fn) { 53 | 54 | return function (...args) { 55 | 56 | const callback = args.pop(); 57 | 58 | if (typeof callback !== 'function') { 59 | throw new TypeError('Last argument must be a function.'); 60 | } 61 | 62 | let sync = true; 63 | 64 | args.push(function (...innerArgs) { 65 | 66 | if (sync) { 67 | setImmediate(function () { 68 | 69 | callback.apply(null, innerArgs); 70 | }); 71 | } 72 | else { 73 | callback.apply(null, innerArgs); 74 | } 75 | }); 76 | 77 | fn.apply(this, args); 78 | sync = false; 79 | }; 80 | }; 81 | 82 | 83 | module.exports.isArrayLike = function (arr) { 84 | 85 | return Array.isArray(arr) || (typeof arr === 'object' && 86 | arr !== null && 87 | arr.length >= 0 && 88 | arr.length >>> 0 === arr.length); 89 | }; 90 | 91 | 92 | module.exports.memoize = function (fn, hasher) { 93 | 94 | hasher = hasher || function (item) { 95 | 96 | return item; 97 | }; 98 | 99 | const memo = {}; 100 | const queues = {}; 101 | 102 | const memoized = function (...args) { 103 | 104 | const callback = args.pop(); 105 | const key = hasher.apply(null, args); 106 | 107 | if (key in memo) { 108 | process.nextTick(function () { 109 | 110 | callback.apply(null, memo[key]); 111 | }); 112 | } 113 | else if (key in queues) { 114 | queues[key].push(callback); 115 | } 116 | else { 117 | queues[key] = [callback]; 118 | fn.apply(null, args.concat([function (...args) { 119 | 120 | memo[key] = args; 121 | const q = queues[key]; 122 | delete queues[key]; 123 | 124 | for (let i = 0, l = q.length; i < l; i++) { 125 | q[i].apply(null, args); 126 | } 127 | }])); 128 | } 129 | }; 130 | 131 | memoized.memo = memo; 132 | memoized.unmemoized = fn; 133 | 134 | return memoized; 135 | }; 136 | 137 | 138 | module.exports.unmemoize = function (fn) { 139 | 140 | return function (...args) { 141 | 142 | return (fn.unmemoized || fn).apply(null, args); 143 | }; 144 | }; 145 | 146 | 147 | module.exports.log = internals.consoleFn('log'); 148 | 149 | 150 | module.exports.dir = internals.consoleFn('dir'); 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "insync", 3 | "version": "2.1.1", 4 | "description": "Higher-order functions and common patterns for asynchronous code. Node specific port of async.", 5 | "author": "Continuation Labs (http://continuation.io/)", 6 | "main": "build/index.js", 7 | "homepage": "https://github.com/continuationlabs/insync", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/continuationlabs/insync.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/continuationlabs/insync/issues" 14 | }, 15 | "license": "BSD-3-Clause", 16 | "devDependencies": { 17 | "babel": "5.x.x", 18 | "code": "1.x.x", 19 | "eslint": "1.x.x", 20 | "eslint-config-hapi": "3.x.x", 21 | "eslint-plugin-hapi": "1.x.x", 22 | "lab": "6.x.x" 23 | }, 24 | "scripts": { 25 | "test": "npm run lint && lab -T test/transformer.js -v -t 100 -a code", 26 | "compile": "babel lib --out-dir build", 27 | "lint": "eslint lib", 28 | "prepublish": "npm run compile" 29 | }, 30 | "keywords": [ 31 | "async", 32 | "asynchronous", 33 | "higher-order", 34 | "flow control" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /test/transformer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Babel = require('babel'); 3 | 4 | 5 | module.exports = [ 6 | { 7 | ext: '.js', 8 | transform: function (content, filename) { 9 | 10 | // Make sure to only transform your code or the dependencies you want 11 | if (filename.indexOf('lib') >= 0) { 12 | var result = Babel.transform(content, { 13 | sourceMap: 'inline', 14 | filename: filename, 15 | sourceFileName: filename 16 | }); 17 | return result.code; 18 | } 19 | 20 | return content; 21 | } 22 | } 23 | ]; 24 | --------------------------------------------------------------------------------