├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── bench.js ├── bench_long.js ├── example.js ├── package.json ├── parallel.js └── test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: standard 10 | versions: 11 | - 16.0.3 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [4.x, 6.x, 8.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | - name: Run tests 22 | run: | 23 | npm install 24 | npm run test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like nyc 14 | coverage 15 | .nyc_output 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 | # Commenting this out is preferred by some people, see 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 26 | node_modules 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Matteo Collina 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastparallel [![ci](https://github.com/mcollina/fastparallel/actions/workflows/ci.yml/badge.svg)](https://github.com/mcollina/fastparallel/actions/workflows/ci.yml) 2 | 3 | Zero-overhead parallel function call for node.js. Also supports each 4 | and map! 5 | 6 | Benchmark for doing 3 calls `setImmediate` 1 million times: 7 | 8 | ```text 9 | benchSetImmediate*1000000: 1378.514ms 10 | benchAsyncParallel*1000000: 1740.304ms 11 | benchAsyncEach*1000000: 1566.517ms 12 | benchAsyncMap*1000000: 1687.518ms 13 | benchNeoParallel*1000000: 1388.223ms 14 | benchNeoEach*1000000: 1473.006ms 15 | benchNeoMap*1000000: 1402.986ms 16 | benchInsyncParallel*1000000: 1957.863ms 17 | benchInsyncEach*1000000: 1383.822ms 18 | benchInsyncMap*1000000: 1822.954ms 19 | benchItemsParallel*1000000: 1690.118ms 20 | benchParallelize*1000000: 1570.064ms 21 | benchFastParallel*1000000: 1536.692ms 22 | benchFastParallelNoResults*1000000: 1363.145ms 23 | benchFastParallelEachResults*1000000: 1508.134ms 24 | benchFastParallelEach*1000000: 1325.314ms 25 | ``` 26 | 27 | Obtained on node 12.18.2, on a dedicated server. 28 | 29 | If you need zero-overhead series function call, check out 30 | [fastseries](http://npm.im/fastseries). If you need a fast work queue 31 | check out [fastq](http://npm.im/fastq). If you need to run fast 32 | waterfall calls, use [fastfall](http://npm.im/fastfall). 33 | 34 | [![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard) 35 | 36 | __The major difference between version 1.x.x and 2.x.x is the order of 37 | results__, this is now ready to replace async in every case. 38 | 39 | ## Example for parallel call 40 | 41 | ```js 42 | var parallel = require('fastparallel')({ 43 | // this is a function that will be called 44 | // when a parallel completes 45 | released: completed, 46 | 47 | // if you want the results, then here you are 48 | results: true 49 | }) 50 | 51 | parallel( 52 | {}, // what will be this in the functions 53 | [something, something, something], // functions to call 54 | 42, // the first argument of the functions 55 | done // the function to be called when the parallel ends 56 | ) 57 | 58 | function something (arg, cb) { 59 | setImmediate(cb, null, 'myresult') 60 | } 61 | 62 | function done (err, results) { 63 | console.log('parallel completed, results:', results) 64 | } 65 | 66 | function completed () { 67 | console.log('parallel completed!') 68 | } 69 | ``` 70 | 71 | ## Example for each and map calls 72 | 73 | ```js 74 | var parallel = require('fastparallel')({ 75 | // this is a function that will be called 76 | // when a parallel completes 77 | released: completed, 78 | 79 | // if you want the results, then here you are 80 | // passing false disables map 81 | results: true 82 | }) 83 | 84 | parallel( 85 | {}, // what will be this in the functions 86 | something, // functions to call 87 | [1, 2, 3], // the first argument of the functions 88 | done // the function to be called when the parallel ends 89 | ) 90 | 91 | function something (arg, cb) { 92 | setImmediate(cb, null, 'myresult') 93 | } 94 | 95 | function done (err, results) { 96 | console.log('parallel completed, results:', results) 97 | } 98 | 99 | function completed () { 100 | console.log('parallel completed!') 101 | } 102 | 103 | ``` 104 | 105 | ## Caveats 106 | 107 | The `done` function will be called only once, even if more than one error happen. 108 | 109 | This library works by caching the latest used function, so that running a new parallel 110 | does not cause **any memory allocations**. 111 | 112 | ## Why it is so fast? 113 | 114 | 1. This library is caching functions a lot. 115 | 116 | 2. V8 optimizations: thanks to caching, the functions can be optimized by V8 (if they are optimizable, and I took great care of making them so). 117 | 118 | 3. Don't use arrays if you just need a queue. A linked list implemented via processes is much faster if you don't need to access elements in between. 119 | 120 | 4. Accept passing a this for the functions. Thanks to this hack, you can extract your functions, and place them in a outer level where they are not created at every execution. 121 | 122 | ## License 123 | 124 | ISC 125 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const max = 1000000 4 | const parallel = require('./')() 5 | const parallelNoResults = require('./')({ results: false }) 6 | const bench = require('fastbench') 7 | const async = require('async') 8 | const neo = require('neo-async') 9 | const insync = require('insync') 10 | const items = require('items') 11 | const parallelize = require('parallelize') 12 | 13 | function benchFastParallel (done) { 14 | parallel(null, [somethingP, somethingP, somethingP], 42, done) 15 | } 16 | 17 | function benchFastParallelNoResults (done) { 18 | parallelNoResults(null, [somethingP, somethingP, somethingP], 42, done) 19 | } 20 | 21 | function benchFastParallelEach (done) { 22 | parallelNoResults(null, somethingP, [1, 2, 3], done) 23 | } 24 | 25 | function benchFastParallelEachResults (done) { 26 | parallel(null, somethingP, [1, 2, 3], done) 27 | } 28 | 29 | function benchAsyncParallel (done) { 30 | async.parallel([somethingA, somethingA, somethingA], done) 31 | } 32 | 33 | function benchInsyncParallel (done) { 34 | insync.parallel([somethingA, somethingA, somethingA], done) 35 | } 36 | 37 | function benchNeoParallel (done) { 38 | neo.parallel([somethingA, somethingA, somethingA], done) 39 | } 40 | 41 | function benchItemsParallel (done) { 42 | items.parallel.execute([somethingA, somethingA, somethingA], done) 43 | } 44 | 45 | function benchParallelize (done) { 46 | const next = parallelize(done) 47 | 48 | somethingA(next()) 49 | somethingA(next()) 50 | somethingA(next()) 51 | } 52 | 53 | function benchAsyncEach (done) { 54 | async.each([1, 2, 3], somethingP, done) 55 | } 56 | 57 | function benchNeoEach (done) { 58 | neo.each([1, 2, 3], somethingP, done) 59 | } 60 | 61 | function benchAsyncMap (done) { 62 | async.map([1, 2, 3], somethingP, done) 63 | } 64 | 65 | function benchNeoMap (done) { 66 | neo.map([1, 2, 3], somethingP, done) 67 | } 68 | 69 | function benchInsyncEach (done) { 70 | insync.each([1, 2, 3], somethingP, done) 71 | } 72 | 73 | function benchInsyncMap (done) { 74 | insync.map([1, 2, 3], somethingP, done) 75 | } 76 | 77 | let nextDone 78 | let nextCount 79 | 80 | function benchSetImmediate (done) { 81 | nextCount = 3 82 | nextDone = done 83 | setImmediate(somethingImmediate) 84 | setImmediate(somethingImmediate) 85 | setImmediate(somethingImmediate) 86 | } 87 | 88 | function somethingImmediate () { 89 | nextCount-- 90 | if (nextCount === 0) { 91 | nextDone() 92 | } 93 | } 94 | 95 | function somethingP (arg, cb) { 96 | setImmediate(cb) 97 | } 98 | 99 | function somethingA (cb) { 100 | setImmediate(cb) 101 | } 102 | 103 | const run = bench([ 104 | benchSetImmediate, 105 | benchAsyncParallel, 106 | benchAsyncEach, 107 | benchAsyncMap, 108 | benchNeoParallel, 109 | benchNeoEach, 110 | benchNeoMap, 111 | benchInsyncParallel, 112 | benchInsyncEach, 113 | benchInsyncMap, 114 | benchItemsParallel, 115 | benchParallelize, 116 | benchFastParallel, 117 | benchFastParallelNoResults, 118 | benchFastParallelEachResults, 119 | benchFastParallelEach 120 | ], max) 121 | 122 | run(run) 123 | -------------------------------------------------------------------------------- /bench_long.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const max = 1000000 4 | const parallel = require('./')() 5 | const parallelNoResults = require('./')({ results: false }) 6 | const bench = require('fastbench') 7 | const async = require('async') 8 | const neo = require('neo-async') 9 | 10 | const funcs = [] 11 | 12 | for (let i = 0; i < 25; i++) { 13 | funcs.push(something) 14 | } 15 | 16 | function benchFastParallel (done) { 17 | parallel(null, funcs, 42, done) 18 | } 19 | 20 | function benchFastParallelNoResults (done) { 21 | parallelNoResults(null, funcs, 42, done) 22 | } 23 | 24 | function benchAsyncParallel (done) { 25 | async.parallel(funcs, done) 26 | } 27 | 28 | function benchNeoParallel (done) { 29 | neo.parallel(funcs, done) 30 | } 31 | 32 | function something (cb) { 33 | setImmediate(cb) 34 | } 35 | 36 | const run = bench([ 37 | benchAsyncParallel, 38 | benchNeoParallel, 39 | benchFastParallel, 40 | benchFastParallelNoResults 41 | ], max) 42 | 43 | run(run) 44 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const parallel = require('./')({ 4 | // this is a function that will be called 5 | // when a parallel completes 6 | released: completed, 7 | 8 | // we want results and errors 9 | // passing false will make it faster! 10 | results: true 11 | }) 12 | 13 | parallel( 14 | {}, // what will be this in the functions 15 | [something, something, something], // functions to call 16 | 42, // the first argument of the functions 17 | next // the function to be called when the parallel ends 18 | ) 19 | 20 | function something (arg, cb) { 21 | setImmediate(cb, null, 'myresult') 22 | } 23 | 24 | function next (err, results) { 25 | if (err) { 26 | // do something here! 27 | } 28 | console.log('parallel completed, results:', results) 29 | 30 | parallel({}, something, [1, 2, 3], done) 31 | } 32 | 33 | function done (err, results) { 34 | if (err) { 35 | // do something here! 36 | } 37 | console.log('parallel completed, results:', results) 38 | } 39 | 40 | function completed () { 41 | console.log('parallel completed!') 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastparallel", 3 | "version": "2.4.1", 4 | "description": "Zero-overhead asynchronous parallel/each/map function call", 5 | "main": "parallel.js", 6 | "scripts": { 7 | "lint": "standard", 8 | "test": "tape test.js | faucet", 9 | "coverage": "nyc --reporter=lcov tape test.js; cat coverage/lcov.info | coveralls" 10 | }, 11 | "pre-commit": [ 12 | "lint", 13 | "test" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mcollina/fastparallel.git" 18 | }, 19 | "keywords": [ 20 | "parallel", 21 | "fast", 22 | "async" 23 | ], 24 | "author": "Matteo Collina ", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/mcollina/fastparallel/issues" 28 | }, 29 | "homepage": "https://github.com/mcollina/fastparallel", 30 | "devDependencies": { 31 | "async": "^3.2.6", 32 | "coveralls": "^3.1.1", 33 | "fastbench": "^1.0.1", 34 | "faucet": "0.0.4", 35 | "insync": "^2.1.1", 36 | "items": "^2.2.1", 37 | "neo-async": "^2.6.2", 38 | "nyc": "^17.1.0", 39 | "parallelize": "^3.0.1", 40 | "pre-commit": "^1.2.2", 41 | "standard": "^17.1.2", 42 | "tape": "^5.9.0" 43 | }, 44 | "dependencies": { 45 | "reusify": "^1.1.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /parallel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const reusify = require('reusify') 4 | const defaults = { 5 | released: nop, 6 | results: true 7 | } 8 | 9 | function fastparallel (options) { 10 | options = Object.assign({}, defaults, options) 11 | 12 | const released = options.released 13 | const queue = reusify(options.results ? ResultsHolder : NoResultsHolder) 14 | const queueSingleCaller = reusify(SingleCaller) 15 | const goArray = options.results ? goResultsArray : goNoResultsArray 16 | const goFunc = options.results ? goResultsFunc : goNoResultsFunc 17 | 18 | return parallel 19 | 20 | function parallel (that, toCall, arg, done) { 21 | const holder = queue.get() 22 | done = done || nop 23 | if (toCall.length === 0) { 24 | done.call(that) 25 | released(holder) 26 | } else { 27 | holder._callback = done 28 | holder._callThat = that 29 | holder._release = release 30 | 31 | if (typeof toCall === 'function') { 32 | goFunc(that, toCall, arg, holder) 33 | } else { 34 | goArray(that, toCall, arg, holder) 35 | } 36 | 37 | if (holder._count === 0) { 38 | holder.release() 39 | } 40 | } 41 | } 42 | 43 | function release (holder) { 44 | queue.release(holder) 45 | released(holder) 46 | } 47 | 48 | function singleCallerRelease (holder) { 49 | queueSingleCaller.release(holder) 50 | } 51 | 52 | function goResultsFunc (that, toCall, arg, holder) { 53 | let singleCaller = null 54 | holder._count = arg.length 55 | holder._results = new Array(holder._count) 56 | for (let i = 0; i < arg.length; i++) { 57 | singleCaller = queueSingleCaller.get() 58 | singleCaller._release = singleCallerRelease 59 | singleCaller.parent = holder 60 | singleCaller.pos = i 61 | if (that) { 62 | toCall.call(that, arg[i], singleCaller.release) 63 | } else { 64 | toCall(arg[i], singleCaller.release) 65 | } 66 | } 67 | } 68 | 69 | function goResultsArray (that, funcs, arg, holder) { 70 | let sc = null 71 | let tc = nop 72 | holder._count = funcs.length 73 | holder._results = new Array(holder._count) 74 | for (let i = 0; i < funcs.length; i++) { 75 | sc = queueSingleCaller.get() 76 | sc._release = singleCallerRelease 77 | sc.parent = holder 78 | sc.pos = i 79 | tc = funcs[i] 80 | if (that) { 81 | if (tc.length === 1) tc.call(that, sc.release) 82 | else tc.call(that, arg, sc.release) 83 | } else { 84 | if (tc.length === 1) tc(sc.release) 85 | else tc(arg, sc.release) 86 | } 87 | } 88 | } 89 | 90 | function goNoResultsFunc (that, toCall, arg, holder) { 91 | holder._count = arg.length 92 | for (let i = 0; i < arg.length; i++) { 93 | if (that) { 94 | toCall.call(that, arg[i], holder.release) 95 | } else { 96 | toCall(arg[i], holder.release) 97 | } 98 | } 99 | } 100 | 101 | function goNoResultsArray (that, funcs, arg, holder) { 102 | let toCall = null 103 | holder._count = funcs.length 104 | for (let i = 0; i < funcs.length; i++) { 105 | toCall = funcs[i] 106 | if (that) { 107 | if (toCall.length === 1) { 108 | toCall.call(that, holder.release) 109 | } else { 110 | toCall.call(that, arg, holder.release) 111 | } 112 | } else { 113 | if (toCall.length === 1) { 114 | toCall(holder.release) 115 | } else { 116 | toCall(arg, holder.release) 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | function NoResultsHolder () { 124 | this._count = -1 125 | this._callback = nop 126 | this._callThat = null 127 | this._release = null 128 | this.next = null 129 | 130 | const that = this 131 | let i = 0 132 | this.release = function () { 133 | const cb = that._callback 134 | if (++i === that._count || that._count === 0) { 135 | if (that._callThat) { 136 | cb.call(that._callThat) 137 | } else { 138 | cb() 139 | } 140 | that._callback = nop 141 | that._callThat = null 142 | i = 0 143 | that._release(that) 144 | } 145 | } 146 | } 147 | 148 | function SingleCaller () { 149 | this.pos = -1 150 | this._release = nop 151 | this.parent = null 152 | this.next = null 153 | 154 | const that = this 155 | this.release = function (err, result) { 156 | that.parent.release(err, that.pos, result) 157 | that.pos = -1 158 | that.parent = null 159 | that._release(that) 160 | } 161 | } 162 | 163 | function ResultsHolder () { 164 | this._count = -1 165 | this._callback = nop 166 | this._results = null 167 | this._err = null 168 | this._callThat = null 169 | this._release = nop 170 | this.next = null 171 | 172 | const that = this 173 | let i = 0 174 | this.release = function (err, pos, result) { 175 | that._err = that._err || err 176 | if (pos >= 0) { 177 | that._results[pos] = result 178 | } 179 | const cb = that._callback 180 | if (++i === that._count || that._count === 0) { 181 | if (that._callThat) { 182 | cb.call(that._callThat, that._err, that._results) 183 | } else { 184 | cb(that._err, that._results) 185 | } 186 | that._callback = nop 187 | that._results = null 188 | that._err = null 189 | that._callThat = null 190 | i = 0 191 | that._release(that) 192 | } 193 | } 194 | } 195 | 196 | function nop () { } 197 | 198 | module.exports = fastparallel 199 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const parallel = require('./') 5 | 6 | test('basically works', function (t) { 7 | t.plan(6) 8 | 9 | const instance = parallel({ 10 | released 11 | }) 12 | let count = 0 13 | const obj = {} 14 | 15 | instance(obj, [something, something], 42, function done () { 16 | t.equal(count, 2, 'all functions must have completed') 17 | }) 18 | 19 | function something (arg, cb) { 20 | t.equal(obj, this) 21 | t.equal(arg, 42) 22 | setImmediate(function () { 23 | count++ 24 | cb() 25 | }) 26 | } 27 | 28 | function released () { 29 | t.pass('release') 30 | } 31 | }) 32 | 33 | test('accumulates results', function (t) { 34 | t.plan(8) 35 | 36 | const instance = parallel({ 37 | released 38 | }) 39 | let count = 0 40 | const obj = {} 41 | 42 | instance(obj, [something, something], 42, function done (err, results) { 43 | t.notOk(err, 'no error') 44 | t.equal(count, 2, 'all functions must have completed') 45 | t.deepEqual(results, [1, 2]) 46 | }) 47 | 48 | function something (arg, cb) { 49 | t.equal(obj, this) 50 | t.equal(arg, 42) 51 | setImmediate(function () { 52 | count++ 53 | cb(null, count) 54 | }) 55 | } 56 | 57 | function released () { 58 | t.pass() 59 | } 60 | }) 61 | 62 | test('fowards errs', function (t) { 63 | t.plan(3) 64 | 65 | const instance = parallel({ 66 | released 67 | }) 68 | let count = 0 69 | const obj = {} 70 | 71 | instance(obj, [somethingErr, something], 42, function done (err, results) { 72 | t.ok(err) 73 | t.equal(count, 2, 'all functions must have completed') 74 | }) 75 | 76 | function something (arg, cb) { 77 | setImmediate(function () { 78 | count++ 79 | cb(null, count) 80 | }) 81 | } 82 | 83 | function somethingErr (arg, cb) { 84 | setImmediate(function () { 85 | count++ 86 | cb(new Error('this is an err!')) 87 | }) 88 | } 89 | 90 | function released () { 91 | t.pass() 92 | } 93 | }) 94 | 95 | test('fowards errs (bis)', function (t) { 96 | t.plan(3) 97 | 98 | const instance = parallel({ 99 | released 100 | }) 101 | let count = 0 102 | const obj = {} 103 | 104 | instance(obj, [something, somethingErr], 42, function done (err, results) { 105 | t.ok(err) 106 | t.equal(count, 2, 'all functions must have completed') 107 | }) 108 | 109 | function something (arg, cb) { 110 | setImmediate(function () { 111 | count++ 112 | cb(null, count) 113 | }) 114 | } 115 | 116 | function somethingErr (arg, cb) { 117 | setImmediate(function () { 118 | count++ 119 | cb(new Error('this is an err!')) 120 | }) 121 | } 122 | 123 | function released () { 124 | t.pass() 125 | } 126 | }) 127 | 128 | test('does not forward errors or result with results:false flag', function (t) { 129 | t.plan(8) 130 | 131 | const instance = parallel({ 132 | released, 133 | results: false 134 | }) 135 | let count = 0 136 | const obj = {} 137 | 138 | instance(obj, [something, something], 42, function done (err, results) { 139 | t.equal(err, undefined, 'no err') 140 | t.equal(results, undefined, 'no err') 141 | t.equal(count, 2, 'all functions must have completed') 142 | }) 143 | 144 | function something (arg, cb) { 145 | t.equal(obj, this) 146 | t.equal(arg, 42) 147 | setImmediate(function () { 148 | count++ 149 | cb() 150 | }) 151 | } 152 | 153 | function released () { 154 | t.pass() 155 | } 156 | }) 157 | 158 | test('should call done and released if an empty is passed', function (t) { 159 | t.plan(2) 160 | 161 | const instance = parallel({ 162 | released 163 | }) 164 | const obj = {} 165 | 166 | instance(obj, [], 42, function done () { 167 | t.pass() 168 | }) 169 | 170 | function released () { 171 | t.pass() 172 | } 173 | }) 174 | 175 | test('each support', function (t) { 176 | t.plan(8) 177 | 178 | const instance = parallel({ 179 | released 180 | }) 181 | let count = 0 182 | const obj = {} 183 | const args = [1, 2, 3] 184 | let i = 0 185 | 186 | instance(obj, something, args, function done () { 187 | t.equal(count, 3, 'all functions must have completed') 188 | }) 189 | 190 | function something (arg, cb) { 191 | t.equal(obj, this, 'this matches') 192 | t.equal(args[i++], arg, 'the arg is correct') 193 | setImmediate(function () { 194 | count++ 195 | cb() 196 | }) 197 | } 198 | 199 | function released () { 200 | t.pass() 201 | } 202 | }) 203 | 204 | test('call the callback with the given this', function (t) { 205 | t.plan(1) 206 | 207 | const instance = parallel() 208 | const obj = {} 209 | 210 | instance(obj, [build(), build()], 42, function done () { 211 | t.equal(obj, this, 'this matches') 212 | }) 213 | 214 | function build () { 215 | return function something (arg, cb) { 216 | setImmediate(cb) 217 | } 218 | } 219 | }) 220 | 221 | test('call the callback with the given this with no results', function (t) { 222 | t.plan(1) 223 | 224 | const instance = parallel({ results: false }) 225 | const obj = {} 226 | 227 | instance(obj, [build(), build()], 42, function done () { 228 | t.equal(obj, this, 'this matches') 229 | }) 230 | 231 | function build () { 232 | return function something (arg, cb) { 233 | setImmediate(cb) 234 | } 235 | } 236 | }) 237 | 238 | test('call the callback with the given this with no data', function (t) { 239 | t.plan(1) 240 | 241 | const instance = parallel() 242 | const obj = {} 243 | 244 | instance(obj, [], 42, function done () { 245 | t.equal(obj, this, 'this matches') 246 | }) 247 | }) 248 | 249 | test('call the result callback when the each array is empty', function (t) { 250 | t.plan(1) 251 | 252 | const instance = parallel() 253 | const obj = {} 254 | 255 | instance(obj, something, [], function done () { 256 | t.pass('the result function has been called') 257 | }) 258 | 259 | function something (arg, cb) { 260 | t.error('this should never be called') 261 | } 262 | }) 263 | 264 | test('call the result callback when the each array is empty with no results', function (t) { 265 | t.plan(1) 266 | 267 | const instance = parallel({ results: false }) 268 | const obj = {} 269 | 270 | instance(obj, something, [], function done () { 271 | t.pass('the result function has been called') 272 | }) 273 | 274 | function something (arg, cb) { 275 | t.error('this should never be called') 276 | } 277 | }) 278 | 279 | test('does not require a done callback', function (t) { 280 | t.plan(4) 281 | 282 | const instance = parallel() 283 | const obj = {} 284 | 285 | instance(obj, [something, something], 42) 286 | 287 | function something (arg, cb) { 288 | t.equal(obj, this) 289 | t.equal(arg, 42) 290 | setImmediate(cb) 291 | } 292 | }) 293 | 294 | test('works with sync functions with no results', function (t) { 295 | t.plan(6) 296 | 297 | const instance = parallel({ 298 | results: false, 299 | released 300 | }) 301 | let count = 0 302 | const obj = {} 303 | 304 | instance(obj, [something, something], 42, function done () { 305 | t.equal(2, count, 'all functions must have completed') 306 | }) 307 | 308 | function something (arg, cb) { 309 | t.equal(this, obj) 310 | t.equal(42, arg) 311 | count++ 312 | cb() 313 | } 314 | 315 | function released () { 316 | t.pass('release') 317 | } 318 | }) 319 | 320 | test('accumulates results in order', function (t) { 321 | t.plan(8) 322 | 323 | const instance = parallel({ 324 | released 325 | }) 326 | let count = 2 327 | const obj = {} 328 | 329 | instance(obj, [something, something], 42, function done (err, results) { 330 | t.notOk(err, 'no error') 331 | t.equal(count, 0, 'all functions must have completed') 332 | t.deepEqual(results, [2, 1]) 333 | }) 334 | 335 | function something (arg, cb) { 336 | t.equal(obj, this) 337 | t.equal(arg, 42) 338 | const value = count-- 339 | setTimeout(function () { 340 | cb(null, value) 341 | }, 10 * value) 342 | } 343 | 344 | function released () { 345 | t.pass() 346 | } 347 | }) 348 | 349 | test('call without arg if there is no arg with no results', function (t) { 350 | t.plan(3) 351 | 352 | const instance = parallel({ 353 | results: false 354 | }) 355 | let count = 0 356 | const obj = {} 357 | 358 | instance(obj, [something, something], 42, function done () { 359 | t.equal(count, 2, 'all functions must have completed') 360 | }) 361 | 362 | function something (cb) { 363 | t.equal(obj, this) 364 | setImmediate(function () { 365 | count++ 366 | cb() 367 | }) 368 | } 369 | }) 370 | 371 | test('call without arg if there is no arg with results', function (t) { 372 | t.plan(3) 373 | 374 | const instance = parallel() 375 | let count = 0 376 | const obj = {} 377 | 378 | instance(obj, [something, something], 42, function done () { 379 | t.equal(count, 2, 'all functions must have completed') 380 | }) 381 | 382 | function something (cb) { 383 | t.equal(obj, this) 384 | setImmediate(function () { 385 | count++ 386 | cb() 387 | }) 388 | } 389 | }) 390 | 391 | test('each support with nothing to process', function (t) { 392 | t.plan(2) 393 | 394 | const instance = parallel() 395 | const obj = {} 396 | const args = [] 397 | 398 | instance(obj, something, args, function done (err, results) { 399 | t.error(err) 400 | t.deepEqual(results, [], 'empty results') 401 | }) 402 | 403 | function something (arg, cb) { 404 | t.fail('this should never happen') 405 | } 406 | }) 407 | 408 | test('each without results support with nothing to process', function (t) { 409 | t.plan(1) 410 | 411 | const instance = parallel({ results: false }) 412 | const obj = {} 413 | const args = [] 414 | 415 | instance(obj, something, args, function done () { 416 | t.pass('done called') 417 | }) 418 | 419 | function something (arg, cb) { 420 | t.fail('this should never happen') 421 | } 422 | }) 423 | 424 | test('each works with arrays of objects', function (t) { 425 | t.plan(3) 426 | 427 | const instance = parallel({ results: false }) 428 | const obj = {} 429 | const args = [{ val: true }, { val: true }] 430 | 431 | instance(obj, something, args, function () { 432 | t.ok('done called') 433 | }) 434 | 435 | function something (arg, cb) { 436 | t.ok(arg.val) 437 | cb() 438 | } 439 | }) 440 | 441 | test('using same instance multiple times clears the state of result holder', function (t) { 442 | const total = 10 443 | t.plan(total) 444 | 445 | const instance = parallel({ 446 | results: false, 447 | released 448 | }) 449 | const obj = {} 450 | let count = 0 451 | 452 | function released () { 453 | if (count < total) { 454 | instance(obj, [something], 42, function done () { 455 | t.ok(true, 'done is called') 456 | count++ 457 | }) 458 | } 459 | } 460 | 461 | released() 462 | function something (cb) { 463 | setImmediate(function () { 464 | cb() 465 | }) 466 | } 467 | }) 468 | --------------------------------------------------------------------------------