From 8eed420820436e29f1b1e3b7e0cfead14f4de775 Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Thu, 7 Feb 2019 09:56:38 -0500 Subject: [PATCH 1/5] add aggregate error option --- index.d.ts | 7 +++++++ index.js | 23 ++++++++++++++++++----- index.test-d.ts | 1 + package.json | 3 +++ test.js | 12 ++++++++++++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index 49da529..c999f0a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,6 +6,13 @@ declare namespace pMap { @default Infinity */ concurrency?: number; + + /** + * Throw an aggregate error at the end when all promise are settled. + * + * @default false + */ + aggregateError?: boolean; } /** diff --git a/index.js b/index.js index 36f5c28..04d2b4a 100644 --- a/index.js +++ b/index.js @@ -1,21 +1,24 @@ 'use strict'; +const AggregateError = require('aggregate-error'); const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { options = Object.assign({ - concurrency: Infinity + concurrency: Infinity, + aggregateError: false }, options); if (typeof mapper !== 'function') { throw new TypeError('Mapper function is required'); } - const {concurrency} = options; + const {concurrency, aggregateError} = options; if (!(typeof concurrency === 'number' && concurrency >= 1)) { throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); } const ret = []; + const errors = []; const iterator = iterable[Symbol.iterator](); let isRejected = false; let isIterableDone = false; @@ -35,7 +38,11 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { isIterableDone = true; if (resolvingCount === 0) { - resolve(ret); + if (aggregateError && errors) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } } return; @@ -52,8 +59,14 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { next(); }, error => { - isRejected = true; - reject(error); + if (aggregateError) { + errors.push(error); + resolvingCount--; + next(); + } else { + isRejected = true; + reject(error); + } } ); }; diff --git a/index.test-d.ts b/index.test-d.ts index c53d7c7..6ad8ef2 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -21,6 +21,7 @@ expectType>(multiResultTypeMapper); expectType({}); expectType({concurrency: 0}); +expectType({aggregateError: true}); expectType>(pMap(sites, asyncMapper)); expectType>(pMap(sites, asyncMapper, {concurrency: 2})); diff --git a/package.json b/package.json index 60a866c..7b0d470 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,9 @@ "parallel", "bluebird" ], + "dependencies": { + "aggregate-error": "^2.0.0" + }, "devDependencies": { "ava": "^1.4.1", "delay": "^4.1.0", diff --git a/test.js b/test.js index e941e9d..b2761e4 100644 --- a/test.js +++ b/test.js @@ -4,6 +4,7 @@ import inRange from 'in-range'; import timeSpan from 'time-span'; import randomInt from 'random-int'; import pMap from '.'; +import AggregateError from 'aggregate-error'; const input = [ Promise.resolve([10, 300]), @@ -11,6 +12,13 @@ const input = [ [30, 100] ]; +const errorInput = [ + [20, 200], + [30, 100], + Promise.reject(new Error('foo')), + Promise.reject(new Error('bar')) +]; + const mapper = ([value, ms]) => delay(ms, {value}); test('main', async t => { @@ -69,3 +77,7 @@ test('enforce number in options.concurrency', async t => { await t.notThrowsAsync(pMap([], () => {}, {concurrency: 10})); await t.notThrowsAsync(pMap([], () => {}, {concurrency: Infinity})); }); + +test('aggregate error', async t => { + await t.throwsAsync(m(errorInput, mapper, {concurrency: 1, aggregateError: true}), AggregateError); +}); From 64198fb72a05ba1f998242dec5806dc7dca2f3db Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Wed, 12 Jun 2019 16:58:05 -0400 Subject: [PATCH 2/5] update naming and test --- index.d.ts | 10 +++++----- index.js | 8 ++++---- index.test-d.ts | 2 +- readme.md | 7 +++++++ test.js | 8 +++++--- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/index.d.ts b/index.d.ts index c999f0a..898d15f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,11 +8,11 @@ declare namespace pMap { concurrency?: number; /** - * Throw an aggregate error at the end when all promise are settled. - * - * @default false - */ - aggregateError?: boolean; + If set to true, instead of stop on the first promise reject, wait for all promises and reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. + + @default false + */ + aggregateErrorsWhenDone?: boolean; } /** diff --git a/index.js b/index.js index 04d2b4a..22d8f0b 100644 --- a/index.js +++ b/index.js @@ -4,14 +4,14 @@ const AggregateError = require('aggregate-error'); const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { options = Object.assign({ concurrency: Infinity, - aggregateError: false + aggregateErrorsWhenDone: false }, options); if (typeof mapper !== 'function') { throw new TypeError('Mapper function is required'); } - const {concurrency, aggregateError} = options; + const {concurrency, aggregateErrorsWhenDone} = options; if (!(typeof concurrency === 'number' && concurrency >= 1)) { throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); @@ -38,7 +38,7 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { isIterableDone = true; if (resolvingCount === 0) { - if (aggregateError && errors) { + if (aggregateErrorsWhenDone && errors.length !== 0) { reject(new AggregateError(errors)); } else { resolve(ret); @@ -59,7 +59,7 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { next(); }, error => { - if (aggregateError) { + if (aggregateErrorsWhenDone) { errors.push(error); resolvingCount--; next(); diff --git a/index.test-d.ts b/index.test-d.ts index 6ad8ef2..8137727 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -21,7 +21,7 @@ expectType>(multiResultTypeMapper); expectType({}); expectType({concurrency: 0}); -expectType({aggregateError: true}); +expectType({aggregateErrorsWhenDone: true}); expectType>(pMap(sites, asyncMapper)); expectType>(pMap(sites, asyncMapper, {concurrency: 2})); diff --git a/readme.md b/readme.md index 166f846..5b09577 100644 --- a/readme.md +++ b/readme.md @@ -68,6 +68,13 @@ Minimum: `1` Number of concurrently pending promises returned by `mapper`. +##### aggregateErrorsWhenDone + +Type: `boolean`
+Default: `false`
+ +If set to true, instead of stop on the first promise reject, wait for all promise and rejects with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. + ## Related diff --git a/test.js b/test.js index b2761e4..6e33a14 100644 --- a/test.js +++ b/test.js @@ -3,8 +3,8 @@ import delay from 'delay'; import inRange from 'in-range'; import timeSpan from 'time-span'; import randomInt from 'random-int'; -import pMap from '.'; import AggregateError from 'aggregate-error'; +import pMap from '.'; const input = [ Promise.resolve([10, 300]), @@ -78,6 +78,8 @@ test('enforce number in options.concurrency', async t => { await t.notThrowsAsync(pMap([], () => {}, {concurrency: Infinity})); }); -test('aggregate error', async t => { - await t.throwsAsync(m(errorInput, mapper, {concurrency: 1, aggregateError: true}), AggregateError); +test('aggregate errors', async t => { + await t.notThrowsAsync(pMap(input, mapper, {concurrency: 1, aggregateErrorsWhenDone: true})); + await t.throwsAsync(pMap(errorInput, mapper, {concurrency: 1, aggregateErrorsWhenDone: true}), {instanceOf: AggregateError, message: /foo/}); + await t.throwsAsync(pMap(errorInput, mapper, {concurrency: 1, aggregateErrorsWhenDone: true}), {instanceOf: AggregateError, message: /bar/}); }); From c48234e6014da683030d24ae9011f090ac0b0bcd Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Mon, 8 Jul 2019 10:38:42 -0400 Subject: [PATCH 3/5] update option name and test --- index.d.ts | 6 +++--- index.js | 14 +++++++------- index.test-d.ts | 2 +- readme.md | 6 +++--- test.js | 22 +++++++++++++++++----- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/index.d.ts b/index.d.ts index 898d15f..09362ee 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,11 +8,11 @@ declare namespace pMap { concurrency?: number; /** - If set to true, instead of stop on the first promise reject, wait for all promises and reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. + If set to false, instead of stop on the first promise reject, wait for all promises and reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. - @default false + @default true */ - aggregateErrorsWhenDone?: boolean; + stopOnError?: boolean; } /** diff --git a/index.js b/index.js index 22d8f0b..250c5e7 100644 --- a/index.js +++ b/index.js @@ -4,14 +4,14 @@ const AggregateError = require('aggregate-error'); const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { options = Object.assign({ concurrency: Infinity, - aggregateErrorsWhenDone: false + stopOnError: true }, options); if (typeof mapper !== 'function') { throw new TypeError('Mapper function is required'); } - const {concurrency, aggregateErrorsWhenDone} = options; + const {concurrency, stopOnError} = options; if (!(typeof concurrency === 'number' && concurrency >= 1)) { throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); @@ -38,7 +38,7 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { isIterableDone = true; if (resolvingCount === 0) { - if (aggregateErrorsWhenDone && errors.length !== 0) { + if (!stopOnError && errors.length !== 0) { reject(new AggregateError(errors)); } else { resolve(ret); @@ -59,13 +59,13 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { next(); }, error => { - if (aggregateErrorsWhenDone) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { errors.push(error); resolvingCount--; next(); - } else { - isRejected = true; - reject(error); } } ); diff --git a/index.test-d.ts b/index.test-d.ts index 8137727..c08d0ce 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -21,7 +21,7 @@ expectType>(multiResultTypeMapper); expectType({}); expectType({concurrency: 0}); -expectType({aggregateErrorsWhenDone: true}); +expectType({stopOnError: false}); expectType>(pMap(sites, asyncMapper)); expectType>(pMap(sites, asyncMapper, {concurrency: 2})); diff --git a/readme.md b/readme.md index 5b09577..f944c87 100644 --- a/readme.md +++ b/readme.md @@ -68,12 +68,12 @@ Minimum: `1` Number of concurrently pending promises returned by `mapper`. -##### aggregateErrorsWhenDone +##### stopOnError Type: `boolean`
-Default: `false`
+Default: `true`
-If set to true, instead of stop on the first promise reject, wait for all promise and rejects with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. +If set to false, instead of stop on the first promise reject, wait for all promise and rejects with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. ## Related diff --git a/test.js b/test.js index 6e33a14..227b023 100644 --- a/test.js +++ b/test.js @@ -12,13 +12,20 @@ const input = [ [30, 100] ]; -const errorInput = [ +const errorInput1 = [ [20, 200], [30, 100], Promise.reject(new Error('foo')), Promise.reject(new Error('bar')) ]; +const errorInput2 = [ + [20, 200], + Promise.reject(new Error('bar')), + [30, 100], + Promise.reject(new Error('foo')) +]; + const mapper = ([value, ms]) => delay(ms, {value}); test('main', async t => { @@ -78,8 +85,13 @@ test('enforce number in options.concurrency', async t => { await t.notThrowsAsync(pMap([], () => {}, {concurrency: Infinity})); }); -test('aggregate errors', async t => { - await t.notThrowsAsync(pMap(input, mapper, {concurrency: 1, aggregateErrorsWhenDone: true})); - await t.throwsAsync(pMap(errorInput, mapper, {concurrency: 1, aggregateErrorsWhenDone: true}), {instanceOf: AggregateError, message: /foo/}); - await t.throwsAsync(pMap(errorInput, mapper, {concurrency: 1, aggregateErrorsWhenDone: true}), {instanceOf: AggregateError, message: /bar/}); +test('immediately rejects when stopOnError is true', async t => { + await t.throwsAsync(pMap(errorInput1, mapper, {concurrency: 1}), 'foo'); + await t.throwsAsync(pMap(errorInput2, mapper, {concurrency: 1}), 'bar'); +}); + +test('aggregate errors when stopOnError is false', async t => { + await t.notThrowsAsync(pMap(input, mapper, {concurrency: 1, stopOnError: false})); + await t.throwsAsync(pMap(errorInput1, mapper, {concurrency: 1, stopOnError: false}), {instanceOf: AggregateError, message: /foo(.|\n)*bar/}); + await t.throwsAsync(pMap(errorInput2, mapper, {concurrency: 1, stopOnError: false}), {instanceOf: AggregateError, message: /bar(.|\n)*foo/}); }); From 6839c713bb1e48ca7deaade010553913778c3b0e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 13 Jul 2019 22:02:19 +0700 Subject: [PATCH 4/5] Update index.d.ts --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 09362ee..90269fc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,7 +8,7 @@ declare namespace pMap { concurrency?: number; /** - If set to false, instead of stop on the first promise reject, wait for all promises and reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. + When set to `false`, instead of stopping when a promise rejects, it will wait for all the promises to settle and then reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) containing all the errors from the rejected promises. @default true */ From 52299c353217bbf0f55c6dae4c85dc2e030559f1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 13 Jul 2019 22:02:53 +0700 Subject: [PATCH 5/5] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f944c87..87e7838 100644 --- a/readme.md +++ b/readme.md @@ -71,9 +71,9 @@ Number of concurrently pending promises returned by `mapper`. ##### stopOnError Type: `boolean`
-Default: `true`
+Default: `true` -If set to false, instead of stop on the first promise reject, wait for all promise and rejects with an [aggregated error](https://github.com/sindresorhus/aggregate-error) from all the rejects if there are any. +When set to `false`, instead of stopping when a promise rejects, it will wait for all the promises to settle and then reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) containing all the errors from the rejected promises. ## Related