├── .gitignore ├── .travis.yml ├── .github └── dependabot.yml ├── package.json ├── index.js ├── LICENSE ├── test.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: linux 3 | dist: xenial 4 | node_js: 5 | - 12 6 | - 10 7 | - 8 8 | - 6 9 | - 4 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p-wait-all", 3 | "version": "1.0.1", 4 | "description": "`Promise.all`, but it waits for all promises to settle even if one of them rejected", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test.js" 8 | }, 9 | "keywords": [ 10 | "promise" 11 | ], 12 | "repository": "goto-bus-stop/p-wait-all", 13 | "author": "Renée Kooi ", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "tape": "^5.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function waitAll (promises) { 2 | if (!Array.isArray(promises)) { 3 | // kinda just want to throw here, but Promise.all also rejects 4 | // if the type is incorrect so I'll just follow along 5 | return Promise.reject(new TypeError('p-wait-all: promises must be array')) 6 | } 7 | 8 | return new Promise(function (resolve, reject) { 9 | var ready = 0 10 | var err = null 11 | var results = [] 12 | 13 | checkDone() // if we got an empty array 14 | 15 | for (var i = 0; i < promises.length; i++) (function (p, i) { 16 | p.then(function (result) { 17 | results[i] = result 18 | ready++ 19 | checkDone() 20 | }, function (_err) { 21 | if (!err) err = _err 22 | ready++ 23 | checkDone() 24 | }) 25 | }(promises[i], i)) 26 | 27 | function checkDone () { 28 | if (ready === promises.length) { 29 | if (err) reject(err) 30 | else resolve(results) 31 | } 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Renée Kooi 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 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var waitAll = require('./') 3 | 4 | function delay (ms) { 5 | return new Promise(function (resolve) { 6 | setTimeout(resolve, ms) 7 | }) 8 | } 9 | 10 | test('checks argument type', function (t) { 11 | t.plan(3) 12 | waitAll(null).catch(function (err) { 13 | t.ok(err) 14 | }) 15 | waitAll('abc').catch(function (err) { 16 | t.ok(err) 17 | }) 18 | waitAll({ whatever: 'lol' }).catch(function (err) { 19 | t.ok(err) 20 | }) 21 | }) 22 | 23 | test('collects resolution values like Promise.all', function (t) { 24 | waitAll([ 25 | Promise.resolve(1), 26 | Promise.resolve(2), 27 | Promise.resolve(3) 28 | ]).then(function (values) { 29 | t.equal(values.shift(), 1) 30 | t.equal(values.shift(), 2) 31 | t.equal(values.shift(), 3) 32 | t.end() 33 | }) 34 | }) 35 | 36 | test('rejects if any promises reject', function (t) { 37 | waitAll([ 38 | Promise.resolve(1), 39 | Promise.reject(2), 40 | Promise.resolve(3) 41 | ]).then(function (values) { 42 | t.fail() 43 | }, function (err) { 44 | t.equal(err, 2) 45 | t.end() 46 | }) 47 | }) 48 | 49 | test('waits for promises to settle before rejecting', function (t) { 50 | var didSettle = false 51 | waitAll([ 52 | Promise.resolve(1), 53 | Promise.reject(2), 54 | delay(50).then(function () { didSettle = true }) 55 | ]).then(function (values) { 56 | t.fail() 57 | }, function (err) { 58 | t.equal(err, 2) 59 | t.ok(didSettle) 60 | t.end() 61 | }) 62 | }) 63 | 64 | test('resolves if there are no promises', function (t) { 65 | waitAll([]).then(function () { 66 | t.pass('should resolve if there are no promises') 67 | t.end() 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # p-wait-all 2 | 3 | `Promise.all`, but it waits for all promises to settle even if one of them rejected. 4 | 5 | ## Installation 6 | 7 | With npm: 8 | 9 | ```bash 10 | npm install --save p-wait-all 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | var waitAll = require('p-wait-all') 17 | 18 | var settled = false 19 | var result = waitAll([ 20 | Promise.reject(new Error('operation failed')), 21 | delay(200).then(function () { settled = true }) 22 | ]) 23 | 24 | result.catch(function (err) { 25 | // rejected after 200ms; 26 | // settled === true 27 | }) 28 | ``` 29 | 30 | Compare doing this with `Promise.all`: 31 | 32 | ```js 33 | var settled = false 34 | var result = Promise.all([ 35 | Promise.reject(new Error('operation failed')), 36 | delay(200).then(function () { settled = true }) 37 | ]) 38 | 39 | result.catch(function (err) { 40 | // rejected immediately; 41 | // settled === false 42 | }) 43 | ``` 44 | 45 | This behaviour is useful if you are doing two async operations that may fail individually, so that both operations need to be rolled back. 46 | 47 | For example, saving two related mongoose models, where one may fail (eg. from duplicate key errors): 48 | 49 | ```js 50 | waitAll([ 51 | model1.save(), 52 | relatedModel.save() 53 | ]).catch(function (err) { 54 | var p = [] 55 | if (!model1.isNew) p.push(model1.remove()) 56 | if (!relatedModel.isNew) p.push(relatedModel.remove()) 57 | return Promise.all(p) 58 | }) 59 | ``` 60 | 61 | You cannot remove a model if it is still in the process of being saved, so you have to wait for all the `save()` promises to resolve before attempting to roll them back. 62 | 63 | ## API 64 | 65 | ### result = require('p-wait-all')(promises) 66 | 67 | Wait for `promises` to settle. If any of them errored, reject the `result` promise with the error. If all of them resolved, resolve `result` with an array of resolution values, in order. 68 | 69 | ## License 70 | 71 | [MIT](./LICENSE) 72 | --------------------------------------------------------------------------------