├── LICENSE ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── example.js ├── package.json ├── bench.js ├── series.js ├── README.md └── test.js /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 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 nyc 15 | coverage 16 | .nyc_output 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Commenting this out is preferred by some people, see 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Users Environment Variables 30 | .lock-wscript 31 | .vscode 32 | *code-workspace 33 | 34 | # 0x 35 | flamegraph.html 36 | .__* 37 | profile* 38 | 39 | # mac files 40 | .DS_Store 41 | 42 | # vim swap files 43 | *.swp 44 | -------------------------------------------------------------------------------- /.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: [10.x, 12.x, 13.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | - name: Install 22 | run: | 23 | npm install 24 | 25 | - name: Run tests 26 | run: | 27 | npm run test 28 | 29 | - name: Coveralls Parallel 30 | uses: coverallsapp/github-action@master 31 | with: 32 | github-token: ${{ secrets.github_token }} 33 | parallel: true 34 | 35 | coverage: 36 | needs: test 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Coveralls Finished 40 | uses: coverallsapp/github-action@master 41 | with: 42 | github-token: ${{ secrets.GITHUB_TOKEN }} 43 | parallel-finished: true 44 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var series = require('./')({ 2 | // this is a function that will be called 3 | // when a series completes 4 | released: completed, 5 | 6 | // we want results and errors 7 | // passing false will make it faster! 8 | results: true 9 | }) 10 | 11 | series( 12 | {}, // what will be this in the functions 13 | [something, something, something], // functions to call 14 | 42, // the first argument of the functions 15 | next // the function to be called when the series ends 16 | ) 17 | 18 | function late (arg, cb) { 19 | console.log('finishing', arg) 20 | cb(null, 'myresult-' + arg) 21 | } 22 | 23 | function something (arg, cb) { 24 | setTimeout(late, 1000, arg, cb) 25 | } 26 | 27 | function next (err, results) { 28 | if (err) { 29 | // do something here! 30 | } 31 | console.log('series completed, results:', results) 32 | 33 | series({}, something, [1, 2, 3], done) 34 | } 35 | 36 | function done (err, results) { 37 | if (err) { 38 | // do something here! 39 | } 40 | console.log('series completed, results:', results) 41 | } 42 | 43 | function completed () { 44 | console.log('series completed!') 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastseries", 3 | "version": "2.0.0", 4 | "description": "Zero-overhead asynchronous series/each/map function calls", 5 | "main": "series.js", 6 | "scripts": { 7 | "lint": "standard --verbose | snazzy", 8 | "unit": "tape test.js", 9 | "unit:report": "nyc --reporter=html --reporter=cobertura --reporter=text tape test.js", 10 | "unit:cov": "nyc --reporter=lcovonly tape test.js", 11 | "cov": "nyc --reporter=text tape test.js", 12 | "test:report": "npm run lint && npm run unit:report", 13 | "test": "npm run lint && npm run unit:cov" 14 | }, 15 | "pre-commit": [ 16 | "test" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/mcollina/fastseries.git" 21 | }, 22 | "keywords": [ 23 | "series", 24 | "fast", 25 | "async" 26 | ], 27 | "author": "Matteo Collina ", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/mcollina/fastseries/issues" 31 | }, 32 | "homepage": "https://github.com/mcollina/fastseries", 33 | "devDependencies": { 34 | "async": "^3.2.0", 35 | "fastbench": "^1.0.1", 36 | "neo-async": "^2.6.1", 37 | "nyc": "^15.0.0", 38 | "pre-commit": "^1.2.2", 39 | "snazzy": "^8.0.0", 40 | "standard": "^14.3.1", 41 | "tape": "^4.13.0", 42 | "tiny-each-async": "^2.0.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | var max = 1000000 2 | var series = require('./')() 3 | var seriesNoResults = require('./')({ results: false }) 4 | var async = require('async') 5 | var neo = require('neo-async') 6 | var bench = require('fastbench') 7 | var tinyEachAsync = require('tiny-each-async') 8 | 9 | function benchFastSeries (done) { 10 | series(null, [somethingP, somethingP, somethingP], 42, done) 11 | } 12 | 13 | function benchFastSeriesNoResults (done) { 14 | seriesNoResults(null, [somethingP, somethingP, somethingP], 42, done) 15 | } 16 | 17 | function benchFastSeriesEach (done) { 18 | seriesNoResults(null, somethingP, [1, 2, 3], done) 19 | } 20 | 21 | function benchFastSeriesEachResults (done) { 22 | series(null, somethingP, [1, 2, 3], done) 23 | } 24 | 25 | function benchAsyncSeries (done) { 26 | async.series([somethingA, somethingA, somethingA], done) 27 | } 28 | 29 | function benchAsyncEachSeries (done) { 30 | async.eachSeries([1, 2, 3], somethingP, done) 31 | } 32 | 33 | function benchAsyncMapSeries (done) { 34 | async.mapSeries([1, 2, 3], somethingP, done) 35 | } 36 | 37 | function benchNeoSeries (done) { 38 | neo.series([somethingA, somethingA, somethingA], done) 39 | } 40 | 41 | function benchNeoEachSeries (done) { 42 | neo.eachSeries([1, 2, 3], somethingP, done) 43 | } 44 | 45 | function benchNeoMapSeries (done) { 46 | neo.mapSeries([1, 2, 3], somethingP, done) 47 | } 48 | 49 | function benchTinyEachAsync (done) { 50 | tinyEachAsync([1, 2, 3], 1, somethingP, done) 51 | } 52 | 53 | var nextDone 54 | var nextCount 55 | 56 | function benchSetImmediate (done) { 57 | nextCount = 3 58 | nextDone = done 59 | setImmediate(somethingImmediate) 60 | } 61 | 62 | function somethingImmediate () { 63 | nextCount-- 64 | if (nextCount === 0) { 65 | nextDone() 66 | } else { 67 | setImmediate(somethingImmediate) 68 | } 69 | } 70 | 71 | function somethingP (arg, cb) { 72 | setImmediate(cb) 73 | } 74 | 75 | function somethingA (cb) { 76 | setImmediate(cb) 77 | } 78 | 79 | var run = bench([ 80 | benchSetImmediate, 81 | benchAsyncSeries, 82 | benchAsyncEachSeries, 83 | benchAsyncMapSeries, 84 | benchNeoSeries, 85 | benchNeoEachSeries, 86 | benchNeoMapSeries, 87 | benchTinyEachAsync, 88 | benchFastSeries, 89 | benchFastSeriesNoResults, 90 | benchFastSeriesEach, 91 | benchFastSeriesEachResults 92 | ], max) 93 | 94 | run(run) 95 | -------------------------------------------------------------------------------- /series.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var defaults = { 4 | results: true 5 | } 6 | 7 | function fastseries (options) { 8 | options = Object.assign({}, defaults, options) 9 | 10 | var seriesEach 11 | var seriesList 12 | 13 | if (options.results) { 14 | seriesEach = resultEach 15 | seriesList = resultList 16 | } else { 17 | seriesEach = noResultEach 18 | seriesList = noResultList 19 | } 20 | 21 | return series 22 | 23 | function series (that, toCall, arg, done) { 24 | done = (done || nop).bind(that) 25 | 26 | if (toCall.length === 0) { 27 | done.call(that) 28 | } else if (toCall.bind) { 29 | if (that) { 30 | toCall = toCall.bind(that) 31 | } 32 | seriesEach(toCall, arg, done) 33 | } else { 34 | var _list 35 | if (that) { 36 | var length = toCall.length 37 | _list = new Array(length) 38 | for (var i = 0; i < length; i++) { 39 | _list[i] = toCall[i].bind(that) 40 | } 41 | } else { 42 | _list = toCall 43 | } 44 | 45 | seriesList(_list, arg, done) 46 | } 47 | } 48 | } 49 | 50 | function noResultEach (each, list, cb) { 51 | var i = 0 52 | var length = list.length 53 | 54 | release() 55 | 56 | function release () { 57 | if (i < length) { 58 | makeCallTwo(each, list[i++], release) 59 | } else { 60 | cb() 61 | } 62 | } 63 | } 64 | 65 | function noResultList (list, arg, cb) { 66 | var i = 0 67 | var length = list.length 68 | var makeCall 69 | 70 | if (list[0].length === 1) { 71 | makeCall = makeCallOne 72 | } else { 73 | makeCall = makeCallTwo 74 | } 75 | 76 | release() 77 | 78 | function release () { 79 | if (i < length) { 80 | makeCall(list[i++], arg, release) 81 | } else { 82 | cb() 83 | } 84 | } 85 | } 86 | 87 | function resultEach (each, list, cb) { 88 | var i = 0 89 | var length = list.length 90 | var results = new Array(length) 91 | 92 | release(null, null) 93 | 94 | function release (err, result) { 95 | if (err) { 96 | cb(err) 97 | return 98 | } 99 | 100 | if (i > 0) { 101 | results[i - 1] = result 102 | } 103 | 104 | if (i < length) { 105 | makeCallTwo(each, list[i++], release) 106 | } else { 107 | cb(null, results) 108 | } 109 | } 110 | } 111 | 112 | function resultList (list, arg, cb) { 113 | var i = 0 114 | var length = list.length 115 | var makeCall 116 | 117 | if (list[0].length === 1) { 118 | makeCall = makeCallOne 119 | } else { 120 | makeCall = makeCallTwo 121 | } 122 | 123 | var results = new Array(length) 124 | 125 | release(null, null) 126 | 127 | function release (err, result) { 128 | if (err) { 129 | cb(err) 130 | return 131 | } 132 | 133 | if (i > 0) { 134 | results[i - 1] = result 135 | } 136 | 137 | if (i < length) { 138 | makeCall(list[i++], arg, release) 139 | } else { 140 | cb(null, results) 141 | } 142 | } 143 | } 144 | 145 | function makeCallOne (cb, arg, release) { 146 | cb(release) 147 | } 148 | 149 | function makeCallTwo (cb, arg, release) { 150 | cb(arg, release) 151 | } 152 | 153 | function nop () { } 154 | 155 | module.exports = fastseries 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastseries 2 | 3 | ![ci][ci-url] 4 | [![npm version][npm-badge]][npm-url] 5 | [![Coverage Status][coveralls-badge]][coveralls-url] 6 | [![Dependency Status][david-badge]][david-url] 7 | 8 | Zero-overhead series function call for node.js. 9 | Also supports `each` and `map`! 10 | 11 | If you need zero-overhead parallel function call, check out 12 | [fastparallel](http://npm.im/fastparallel). 13 | 14 | [![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard) 15 | 16 | ## Example for series call 17 | 18 | ```js 19 | var series = require('fastseries')({ 20 | // if you want the results, then here you are 21 | results: true 22 | }) 23 | 24 | series( 25 | {}, // what will be this in the functions 26 | [something, something, something], // functions to call 27 | 42, // the first argument of the functions 28 | done // the function to be called when the series ends 29 | ) 30 | 31 | function late (arg, cb) { 32 | console.log('finishing', arg) 33 | cb(null, 'myresult-' + arg) 34 | } 35 | 36 | function something (arg, cb) { 37 | setTimeout(late, 1000, arg, cb) 38 | } 39 | 40 | function done (err, results) { 41 | console.log('series completed, results:', results) 42 | } 43 | ``` 44 | 45 | ## Example for each and map calls 46 | 47 | ```js 48 | var series = require('fastseries')({ 49 | // if you want the results, then here you are 50 | // passing false disables map 51 | results: true 52 | }) 53 | 54 | series( 55 | {}, // what will be this in the functions 56 | something, // functions to call 57 | [1, 2, 3], // the first argument of the functions 58 | done // the function to be called when the series ends 59 | ) 60 | 61 | function late (arg, cb) { 62 | console.log('finishing', arg) 63 | cb(null, 'myresult-' + arg) 64 | } 65 | 66 | function something (arg, cb) { 67 | setTimeout(late, 1000, arg, cb) 68 | } 69 | 70 | function done (err, results) { 71 | console.log('series completed, results:', results) 72 | } 73 | ``` 74 | 75 | ## Caveats 76 | 77 | The `done` function will be called only once, even if more than one error happen. 78 | 79 | This library works by caching the latest used function, so that running a new series 80 | does not cause **any memory allocations**. 81 | 82 | ## Benchmarks 83 | 84 | Benchmark for doing 3 calls `setImmediate` 1 million times: 85 | 86 | ``` 87 | benchSetImmediate*1000000: 2460.623ms 88 | benchAsyncSeries*1000000: 3064.569ms 89 | benchAsyncEachSeries*1000000: 2913.525ms 90 | benchAsyncMapSeries*1000000: 3020.794ms 91 | benchNeoSeries*1000000: 2617.064ms 92 | benchNeoEachSeries*1000000: 2621.672ms 93 | benchNeoMapSeries*1000000: 2611.294ms 94 | benchTinyEachAsync*1000000: 2706.457ms 95 | benchFastSeries*1000000: 2540.653ms 96 | benchFastSeriesNoResults*1000000: 2538.674ms 97 | benchFastSeriesEach*1000000: 2534.856ms 98 | benchFastSeriesEachResults*1000000: 2545.394ms 99 | ``` 100 | 101 | Benchmarks taken on Node 12.16.1 on a dedicated server. 102 | 103 | See [bench.js](./bench.js) for mode details. 104 | 105 | ## License 106 | 107 | ISC 108 | 109 | [ci-url]: https://github.com/mcollina/fastseries/workflows/ci/badge.svg 110 | [npm-badge]: https://badge.fury.io/js/fastseries.svg 111 | [npm-url]: https://badge.fury.io/js/fastseries 112 | [coveralls-badge]:https://coveralls.io/repos/mcollina/fastseries/badge.svg?branch=master&service=github 113 | [coveralls-url]: https://coveralls.io/github/mcollina/fastseries?branch=master 114 | [david-badge]: https://david-dm.org/mcollina/fastseries.svg 115 | [david-url]: https://david-dm.org/mcollina/fastseries 116 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var series = require('./') 5 | 6 | test('basically works', function (t) { 7 | t.plan(7) 8 | 9 | var instance = series() 10 | var count = 0 11 | var obj = {} 12 | 13 | instance(obj, [build(0), build(1)], 42, function done () { 14 | t.equal(count, 2, 'all functions must have completed') 15 | }) 16 | 17 | function build (expected) { 18 | return function something (arg, cb) { 19 | t.equal(obj, this) 20 | t.equal(arg, 42) 21 | t.equal(expected, count) 22 | setImmediate(function () { 23 | count++ 24 | cb() 25 | }) 26 | } 27 | } 28 | }) 29 | 30 | test('without this', function (t) { 31 | t.plan(7) 32 | 33 | var instance = series() 34 | var count = 0 35 | 36 | instance(null, [build(0), build(1)], 42, function done () { 37 | t.equal(count, 2, 'all functions must have completed') 38 | }) 39 | 40 | function build (expected) { 41 | return function something (arg, cb) { 42 | t.equal(undefined, this) 43 | t.equal(arg, 42) 44 | t.equal(expected, count) 45 | setImmediate(function () { 46 | count++ 47 | cb() 48 | }) 49 | } 50 | } 51 | }) 52 | 53 | test('accumulates results', function (t) { 54 | t.plan(7) 55 | 56 | var instance = series() 57 | var count = 0 58 | var obj = {} 59 | 60 | instance(obj, [something, something], 42, function done (err, results) { 61 | t.notOk(err, 'no error') 62 | t.equal(count, 2, 'all functions must have completed') 63 | t.deepEqual(results, [1, 2]) 64 | }) 65 | 66 | function something (arg, cb) { 67 | t.equal(obj, this) 68 | t.equal(arg, 42) 69 | setImmediate(function () { 70 | count++ 71 | cb(null, count) 72 | }) 73 | } 74 | }) 75 | 76 | test('fowards errs', function (t) { 77 | t.plan(3) 78 | 79 | var instance = series() 80 | var count = 0 81 | var obj = {} 82 | 83 | instance(obj, [somethingErr, something], 42, function done (err, results) { 84 | t.ok(err, 'error exists') 85 | t.equal(err.message, 'this is an err!') 86 | t.equal(count, 1, 'only the first function must have completed') 87 | }) 88 | 89 | function something (arg, cb) { 90 | setImmediate(function () { 91 | count++ 92 | cb(null, count) 93 | }) 94 | } 95 | 96 | function somethingErr (arg, cb) { 97 | setImmediate(function () { 98 | count++ 99 | cb(new Error('this is an err!')) 100 | }) 101 | } 102 | }) 103 | 104 | test('does not forward errors or result with results:false flag', function (t) { 105 | t.plan(7) 106 | 107 | var instance = series({ 108 | results: false 109 | }) 110 | var count = 0 111 | var obj = {} 112 | 113 | instance(obj, [something, something], 42, function done (err, results) { 114 | t.equal(err, undefined, 'no err') 115 | t.equal(results, undefined, 'no err') 116 | t.equal(count, 2, 'all functions must have completed') 117 | }) 118 | 119 | function something (arg, cb) { 120 | t.equal(obj, this) 121 | t.equal(arg, 42) 122 | setImmediate(function () { 123 | count++ 124 | cb() 125 | }) 126 | } 127 | }) 128 | 129 | test('should call done iff an empty is passed', function (t) { 130 | t.plan(1) 131 | 132 | var instance = series() 133 | var obj = {} 134 | 135 | instance(obj, [], 42, function done () { 136 | t.pass() 137 | }) 138 | }) 139 | 140 | test('each support', function (t) { 141 | t.plan(7) 142 | 143 | var instance = series() 144 | var count = 0 145 | var obj = {} 146 | var args = [1, 2, 3] 147 | var i = 0 148 | 149 | instance(obj, something, [].concat(args), function done () { 150 | t.equal(count, 3, 'all functions must have completed') 151 | }) 152 | 153 | function something (arg, cb) { 154 | t.equal(obj, this, 'this matches') 155 | t.equal(args[i++], arg, 'the arg is correct') 156 | setImmediate(function () { 157 | count++ 158 | cb() 159 | }) 160 | } 161 | }) 162 | 163 | test('each errors', function (t) { 164 | t.plan(2) 165 | 166 | var instance = series() 167 | var obj = {} 168 | var args = [1, 2, 3] 169 | var err = new Error('kaboom') 170 | 171 | instance(obj, something, [].concat(args), function done (_err) { 172 | t.equal(err, _err) 173 | }) 174 | 175 | function something (arg, cb) { 176 | t.pass('something called') 177 | cb(err) 178 | } 179 | }) 180 | 181 | test('each without this', function (t) { 182 | t.plan(7) 183 | 184 | var instance = series() 185 | var count = 0 186 | var args = [1, 2, 3] 187 | var i = 0 188 | 189 | instance(null, something, [].concat(args), function done () { 190 | t.equal(count, 3, 'all functions must have completed') 191 | }) 192 | 193 | function something (arg, cb) { 194 | t.equal(undefined, this, 'this matches') 195 | t.equal(args[i++], arg, 'the arg is correct') 196 | setImmediate(function () { 197 | count++ 198 | cb() 199 | }) 200 | } 201 | }) 202 | 203 | test('call the callback with the given this', function (t) { 204 | t.plan(1) 205 | 206 | var instance = series() 207 | var obj = {} 208 | 209 | instance(obj, [build(), build()], 42, function done () { 210 | t.equal(obj, this, 'this matches') 211 | }) 212 | 213 | function build () { 214 | return function something (arg, cb) { 215 | setImmediate(cb) 216 | } 217 | } 218 | }) 219 | 220 | test('call the callback with the given this with no results', function (t) { 221 | t.plan(1) 222 | 223 | var instance = series({ results: false }) 224 | var obj = {} 225 | 226 | instance(obj, [build(), build()], 42, function done () { 227 | t.equal(obj, this, 'this matches') 228 | }) 229 | 230 | function build () { 231 | return function something (arg, cb) { 232 | setImmediate(cb) 233 | } 234 | } 235 | }) 236 | 237 | test('call the callback with the given this with no data', function (t) { 238 | t.plan(1) 239 | 240 | var instance = series() 241 | var obj = {} 242 | 243 | instance(obj, [], 42, function done () { 244 | t.equal(obj, this, 'this matches') 245 | }) 246 | }) 247 | 248 | test('support no final callback', function (t) { 249 | t.plan(6) 250 | 251 | var instance = series() 252 | var count = 0 253 | var obj = {} 254 | 255 | instance(obj, [build(0), build(1)], 42) 256 | 257 | function build (expected) { 258 | return function something (arg, cb) { 259 | t.equal(obj, this) 260 | t.equal(arg, 42) 261 | t.equal(expected, count) 262 | setImmediate(function () { 263 | count++ 264 | cb() 265 | }) 266 | } 267 | } 268 | }) 269 | 270 | test('call without arg if there is no arg with no results', function (t) { 271 | t.plan(3) 272 | 273 | var instance = series({ 274 | results: false 275 | }) 276 | var count = 0 277 | var obj = {} 278 | 279 | instance(obj, [something, something], 42, function done () { 280 | t.equal(count, 2, 'all functions must have completed') 281 | }) 282 | 283 | function something (cb) { 284 | t.equal(obj, this) 285 | setImmediate(function () { 286 | count++ 287 | cb() 288 | }) 289 | } 290 | }) 291 | 292 | test('call without arg if there is no arg with results', function (t) { 293 | t.plan(3) 294 | 295 | var instance = series() 296 | var count = 0 297 | var obj = {} 298 | 299 | instance(obj, [something, something], 42, function done () { 300 | t.equal(count, 2, 'all functions must have completed') 301 | }) 302 | 303 | function something (cb) { 304 | t.equal(obj, this) 305 | setImmediate(function () { 306 | count++ 307 | cb() 308 | }) 309 | } 310 | }) 311 | 312 | test('each support with nothing to process', function (t) { 313 | t.plan(2) 314 | 315 | var instance = series() 316 | var obj = {} 317 | var args = [] 318 | 319 | instance(obj, something, args, function done (err, results) { 320 | t.error(err) 321 | t.deepEqual(results, [], 'empty results') 322 | }) 323 | 324 | function something (arg, cb) { 325 | t.fail('this should never happen') 326 | } 327 | }) 328 | 329 | test('each without results support with nothing to process', function (t) { 330 | t.plan(1) 331 | 332 | var instance = series({ results: false }) 333 | var obj = {} 334 | var args = [] 335 | 336 | instance(obj, something, args, function done () { 337 | t.pass('done called') 338 | }) 339 | 340 | function something (arg, cb) { 341 | t.fail('this should never happen') 342 | } 343 | }) 344 | 345 | test('each without results', function (t) { 346 | t.plan(7) 347 | 348 | var instance = series({ 349 | results: false 350 | }) 351 | var count = 0 352 | var obj = {} 353 | var args = [1, 2, 3] 354 | var i = 0 355 | 356 | instance(obj, something, [].concat(args), function done () { 357 | t.equal(count, 3, 'all functions must have completed') 358 | }) 359 | 360 | function something (arg, cb) { 361 | t.equal(obj, this, 'this matches') 362 | t.equal(args[i++], arg, 'the arg is correct') 363 | setImmediate(function () { 364 | count++ 365 | cb() 366 | }) 367 | } 368 | }) 369 | --------------------------------------------------------------------------------