├── .gitignore ├── .travis.yml ├── API.md ├── LICENSE.md ├── README.md ├── lib └── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/package-lock.json 3 | 4 | coverage.* 5 | 6 | **/.DS_Store 7 | **/._* 8 | 9 | **/*.pem 10 | 11 | **/.vs 12 | **/.vscode 13 | **/.idea 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | version: ~> 1.0 2 | 3 | 4 | import: 5 | - hapijs/ci-config-travis:node_js.yml@main 6 | - hapijs/ci-config-travis:install.yml@main 7 | - hapijs/ci-config-travis:os.yml@main 8 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | ## Usage 3 | 4 | ### `Items.serial(items, method, callback)` 5 | 6 | Runs `method` against each value in the `items` array *in series*. `callback` is executed when all of the tasks are complete. Calling back with an error will short-circuit the remaining tasks. 7 | 8 | - `items` an array of items to pass to `method`. 9 | - `method` a function with the signature `function (item, next, i)`. 10 | - `item` - is the currently processing item in the `items` array. 11 | - `next` - callback function to indicate the end of processing for `item`. Calling `next` with a truthy parameter indicates an error and ends `Items.serial`. 12 | - `i` - The current item's index in the `items` array. 13 | - `callback` - a function with the signature `function (err)`. 14 | - `err` - indicates any errors during processing. 15 | 16 | ### `Items.parallel(items, method, callback)` 17 | 18 | Runs `method` against each value in the `items` array *in parallel*. `callback` is executed when all of the tasks are complete. Calling back with an error will short-circuit the remaining tasks. 19 | 20 | - `items` an array of items to pass to `method`. 21 | - `method` a function with the signature `function (item, next, i)`. 22 | - `item` - is the currently processing item in the `items` array. 23 | - `next` - callback function to indicate the end of processing for `item`. Calling `next` with a truthy parameter indicates an error and ends `Items.parallel`. 24 | - `i` - The current item's index in the `items` array. 25 | - `callback` - a function with the signature `function (err)`. 26 | - `err` - indicates any errors during processing. 27 | 28 | ### `Items.parallel.execute(tasks, callback)` 29 | 30 | Runs all of the functions in `tasks` *in parallel* and stores the results in a collector object passed into `callback`. If any of the tasks callback with an error, the collector object is `null`. 31 | 32 | - `tasks` - on object containing functions to execute in parallel. The `key` of the function is the `key` in the result of collector object. The task should have the signature `function (next)` 33 | - `next(err, result)` - callback function to indicate the end of processing for the current task. 34 | - `err` - indicates any errors during processing. 35 | - `result` - result of this function. This value will be set on the collector object in the final callback. 36 | - `callback(err, result)` 37 | - `err` - any errors reported by *any* of the `tasks`. 38 | - `result` - an object containing the result of running all of the `tasks`. `result` will be `null` if any of the tasks callback with an error. The `result.key` will be the corresponding `key` of the `tasks` object. 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | COMMERCIAL LICENSE 2 | 3 | Copyright (c) 2019-2020 Sideway Inc. 4 | 5 | This package requires a commercial license. You may not use, copy, or distribute it without first acquiring a commercial license from Sideway Inc. Using this software without a license is a violation of US and international law. To obtain a license, please contact [sales@sideway.com](mailto:sales@sideway.com). 6 | 7 | This package contains code previously published under an open source license. You can find the previous materials and the terms under which they were originally published at: [https://github.com/hapijs/items/blob/master/LICENSE](https://github.com/hapijs/items/blob/master/LICENSE). 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # items 4 | 5 | ## Bare minimum async methods adapted specifically for the very limited **hapi** core use cases. 6 | 7 | **items** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together. 8 | 9 | ## Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support 10 | 11 | ## Useful resources 12 | 13 | - [Documentation and API](https://hapi.dev/family/items/) 14 | - [Version status](https://hapi.dev/resources/status/#items) (builds, dependencies, node versions, licenses, eol) 15 | - [Project policies](https://hapi.dev/policies/) 16 | - [Free and commercial support options](https://hapi.dev/support/) 17 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const internals = {}; 4 | 5 | 6 | exports.serial = function (array, method, callback) { 7 | 8 | if (!array.length) { 9 | callback(); 10 | } 11 | else { 12 | let i = 0; 13 | const iterate = function () { 14 | 15 | const done = function (err) { 16 | 17 | if (err) { 18 | callback(err); 19 | } 20 | else { 21 | i = i + 1; 22 | if (i < array.length) { 23 | iterate(); 24 | } 25 | else { 26 | callback(); 27 | } 28 | } 29 | }; 30 | 31 | method(array[i], done, i); 32 | }; 33 | 34 | iterate(); 35 | } 36 | }; 37 | 38 | 39 | exports.parallel = function (array, method, callback) { 40 | 41 | if (!array.length) { 42 | callback(); 43 | } 44 | else { 45 | let count = 0; 46 | let errored = false; 47 | 48 | const done = function (err) { 49 | 50 | if (!errored) { 51 | if (err) { 52 | errored = true; 53 | callback(err); 54 | } 55 | else { 56 | count = count + 1; 57 | if (count === array.length) { 58 | callback(); 59 | } 60 | } 61 | } 62 | }; 63 | 64 | for (let i = 0; i < array.length; ++i) { 65 | method(array[i], done, i); 66 | } 67 | } 68 | }; 69 | 70 | 71 | exports.parallel.execute = function (fnObj, callback) { 72 | 73 | const result = {}; 74 | if (!fnObj) { 75 | return callback(null, result); 76 | } 77 | 78 | const keys = Object.keys(fnObj); 79 | let count = 0; 80 | let errored = false; 81 | 82 | if (!keys.length) { 83 | return callback(null, result); 84 | } 85 | 86 | const done = function (key) { 87 | 88 | return function (err, val) { 89 | 90 | if (!errored) { 91 | if (err) { 92 | errored = true; 93 | callback(err); 94 | } 95 | else { 96 | result[key] = val; 97 | if (++count === keys.length) { 98 | callback(null, result); 99 | } 100 | } 101 | } 102 | }; 103 | }; 104 | 105 | for (let i = 0; i < keys.length; ++i) { 106 | if (!errored) { 107 | const key = keys[i]; 108 | fnObj[key](done(key)); 109 | } 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "items", 3 | "description": "Bare minimum async methods", 4 | "version": "2.2.1", 5 | "repository": "git://github.com/hapijs/items", 6 | "engines": { 7 | "node": ">=6.0.0" 8 | }, 9 | "main": "lib/index.js", 10 | "keywords": [ 11 | "async", 12 | "serial", 13 | "parallel" 14 | ], 15 | "files": [ 16 | "lib" 17 | ], 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "code": "4.x.x", 21 | "lab": "14.x.x" 22 | }, 23 | "scripts": { 24 | "test": "lab -a code -t 100 -L -l", 25 | "test-cov-html": "lab -a code -r html -o coverage.html -l" 26 | }, 27 | "license": "SEE LICENSE IN LICENSE.md" 28 | } 29 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Code = require('code'); 4 | const Items = require('../'); 5 | const Lab = require('lab'); 6 | 7 | 8 | const internals = {}; 9 | 10 | 11 | const lab = exports.lab = Lab.script(); 12 | const describe = lab.experiment; 13 | const it = lab.it; 14 | const expect = Code.expect; 15 | 16 | 17 | describe('Items', () => { 18 | 19 | describe('serial()', () => { 20 | 21 | it('calls methods in serial', (done) => { 22 | 23 | const called = []; 24 | const is = []; 25 | const array = [1, 2, 3, 4, 5]; 26 | const method = function (item, next, i) { 27 | 28 | called.push(item); 29 | is.push(i); 30 | setTimeout(next, 5); 31 | }; 32 | 33 | Items.serial(array, method, (err) => { 34 | 35 | expect(err).to.not.exist(); 36 | expect(called).to.equal(array); 37 | expect(is).to.equal([0, 1, 2, 3, 4]); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('skips on empty array', (done) => { 43 | 44 | const called = []; 45 | const array = []; 46 | const method = function (item, next) { 47 | 48 | called.push(item); 49 | setTimeout(next, 5); 50 | }; 51 | 52 | Items.serial(array, method, (err) => { 53 | 54 | expect(err).to.not.exist(); 55 | expect(called).to.equal(array); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('aborts with error', (done) => { 61 | 62 | const called = []; 63 | const array = [1, 2, 3, 4, 5]; 64 | const method = function (item, next) { 65 | 66 | called.push(item); 67 | if (item === 3) { 68 | return next('error'); 69 | } 70 | 71 | setTimeout(next, 5); 72 | }; 73 | 74 | Items.serial(array, method, (err) => { 75 | 76 | expect(err).to.equal('error'); 77 | expect(called).to.equal([1, 2, 3]); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('parallel()', () => { 84 | 85 | it('calls methods in parallel', (done) => { 86 | 87 | const called = []; 88 | const array = [[1, 1], [2, 4], [3, 2], [4, 3], [5, 5]]; 89 | const is = []; 90 | const method = function (item, next, i) { 91 | 92 | is.push(i); 93 | setTimeout(() => { 94 | 95 | called.push(item[0]); 96 | next(); 97 | }, item[1]); 98 | }; 99 | 100 | Items.parallel(array, method, (err) => { 101 | 102 | expect(err).to.not.exist(); 103 | expect(called).to.equal([1, 3, 4, 2, 5]); 104 | expect(is).to.equal([0, 1, 2, 3, 4]); 105 | done(); 106 | }); 107 | }); 108 | 109 | it('skips on empty array', (done) => { 110 | 111 | const called = []; 112 | const array = []; 113 | const method = function (item, next) { 114 | 115 | setTimeout(() => { 116 | 117 | called.push(item[0]); 118 | next(); 119 | }, item[1]); 120 | }; 121 | 122 | Items.parallel(array, method, (err) => { 123 | 124 | expect(err).to.not.exist(); 125 | expect(called).to.equal([]); 126 | done(); 127 | }); 128 | }); 129 | 130 | it('aborts with error', (done) => { 131 | 132 | const called = []; 133 | const array = [[1, 1], [2, 4], [3, 2], [4, 3], [5, 5]]; 134 | const method = function (item, next) { 135 | 136 | setTimeout(() => { 137 | 138 | if (item[0] === 3) { 139 | return next('error'); 140 | } 141 | 142 | called.push(item[0]); 143 | next(); 144 | }, item[1]); 145 | }; 146 | 147 | Items.parallel(array, method, (err) => { 148 | 149 | expect(err).to.equal('error'); 150 | expect(called).to.equal([1]); 151 | 152 | setTimeout(() => { 153 | 154 | expect(called).to.equal([1, 4, 2, 5]); 155 | done(); 156 | }, 6); 157 | }); 158 | }); 159 | }); 160 | 161 | describe('parallel.execute()', () => { 162 | 163 | it('calls methods in parallel and returns the result', (done) => { 164 | 165 | const fns = { 166 | fn1: function (next) { 167 | 168 | next(null, 'bye'); 169 | }, 170 | fn2: function (next) { 171 | 172 | next(null, 'hi'); 173 | } 174 | }; 175 | 176 | Items.parallel.execute(fns, (err, result) => { 177 | 178 | expect(err).to.not.exist(); 179 | expect(result.fn1).to.equal('bye'); 180 | expect(result.fn2).to.equal('hi'); 181 | done(); 182 | }); 183 | }); 184 | 185 | it('returns an empty object to the callback when passed an empty object', (done) => { 186 | 187 | const fns = {}; 188 | 189 | Items.parallel.execute(fns, (err, result) => { 190 | 191 | expect(err).to.not.exist(); 192 | expect(Object.keys(result).length).to.equal(0); 193 | done(); 194 | }); 195 | }); 196 | 197 | it('returns an empty object to the callback when passed a null object', (done) => { 198 | 199 | Items.parallel.execute(null, (err, result) => { 200 | 201 | expect(err).to.not.exist(); 202 | expect(Object.keys(result).length).to.equal(0); 203 | done(); 204 | }); 205 | }); 206 | 207 | it('exits early and result object is missing when an error is passed to callback', (done) => { 208 | 209 | const fns = { 210 | fn1: function (next) { 211 | 212 | setImmediate(() => { 213 | 214 | next(null, 'hello'); 215 | }); 216 | }, 217 | fn2: function (next) { 218 | 219 | setImmediate(() => { 220 | 221 | next(new Error('This is my error')); 222 | }); 223 | 224 | }, 225 | fn3: function (next) { 226 | 227 | setImmediate(() => { 228 | 229 | next(null, 'bye'); 230 | }); 231 | } 232 | }; 233 | 234 | Items.parallel.execute(fns, (err, result) => { 235 | 236 | expect(err).to.exist(); 237 | expect(result).to.not.exist(); 238 | done(); 239 | }); 240 | }); 241 | 242 | it('exits early and doesn\'t execute other functions on an error', (done) => { 243 | 244 | let fn2Executed = false; 245 | const fns = { 246 | fn1: function (next) { 247 | 248 | next(new Error('This is my error')); 249 | }, 250 | fn2: function (next) { 251 | 252 | setImmediate(() => { 253 | 254 | fn2Executed = true; 255 | next(); 256 | }); 257 | } 258 | }; 259 | 260 | Items.parallel.execute(fns, (err, result) => { 261 | 262 | expect(err).to.exist(); 263 | expect(result).to.not.exist(); 264 | expect(fn2Executed).to.equal(false); 265 | done(); 266 | }); 267 | }); 268 | 269 | it('handles multiple errors being returned by sending first error', (done) => { 270 | 271 | const fns = { 272 | fn1: function (next) { 273 | 274 | next(new Error('fn1')); 275 | }, 276 | fn2: function (next) { 277 | 278 | next(new Error('fn2')); 279 | 280 | }, 281 | fn3: function (next) { 282 | 283 | next(new Error('fn3')); 284 | } 285 | }; 286 | 287 | Items.parallel.execute(fns, (err, result) => { 288 | 289 | expect(err).to.exist(); 290 | expect(result).to.not.exist(); 291 | expect(err.message).to.equal('fn1'); 292 | done(); 293 | }); 294 | }); 295 | }); 296 | }); 297 | --------------------------------------------------------------------------------