Skip to content

Commit

Permalink
settle method [feat]
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Apr 13, 2019
1 parent 0d8e15e commit 0a61214
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 2 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The following methods are provided:
* `forEachSeries( arr, fn )`
* `map( arr, fn [, options] )`
* `mapSeries( arr, fn )`
* `settle( arr )`
* `forIn( obj, fn [, options] )`
* `forInSeries( obj, fn )`
* `forOwn( obj, fn [, options] )`
Expand Down Expand Up @@ -253,6 +254,26 @@ const files = await P.map(
// files = [ 'file contents 1', 'file contents 2', 'file contents 3' ]
```

#### `settle( arr )`

Awaits all promises in array to settle (resolve or reject) and returns an array of the results.

Each result is of form `{ resolved: <boolean>, result: <any> }`.

Never rejects, even if one of the promises in array rejects.

```js
const results = await P.settle( [
Promise.resolve(123),
Promise.reject( new Error('Oops!') )
] );

// results = [
// { resolved: true, result: 123 },
// { resolved: false, result: Error<'Oops!'> }
// ]
```

### Object iteration methods

Object iteration methods iterate over the properties of an object and execute a function on each. The function is expected to return a Promise.
Expand Down
7 changes: 5 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const is = require('./is'),
forInSeries = require('./forInSeries'),
forOwnSeries = require('./forOwnSeries'),
mapInSeries = require('./mapInSeries'),
mapOwnSeries = require('./mapOwnSeries');
mapOwnSeries = require('./mapOwnSeries'),
settle = require('./settle');

// Exports
module.exports = {
Expand Down Expand Up @@ -78,6 +79,7 @@ module.exports = {
forOwnSeries,
mapInSeries,
mapOwnSeries,
settle,

/*
* Longer named exports
Expand Down Expand Up @@ -120,5 +122,6 @@ module.exports = {
promiseForInSeries: forInSeries,
promiseForOwnSeries: forOwnSeries,
promiseMapInSeries: mapInSeries,
promiseMapOwnSeries: mapOwnSeries
promiseMapOwnSeries: mapOwnSeries,
promiseSettle: settle
};
71 changes: 71 additions & 0 deletions lib/settle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* --------------------
* promise-methods module
* `settle` method
* ------------------*/

'use strict';

// Imports
const isPromise = require('./is'),
defer = require('./defer');

// Exports

class Settler {
constructor() {
this.results = [];
this.numAwaiting = 1;
this.deferred = defer();
}

run(promises) {
let i = 0;
for (const promise of promises) {
const index = i;
if (isPromise(promise)) {
this.numAwaiting++;
promise.then(
res => this.resolved(index, res),
err => this.rejected(index, err)
);
} else {
this.results[index] = {resolved: true, result: promise};
}
i++;
}

this.completed();

return this.deferred.promise;
}

resolved(index, result) {
this.results[index] = {resolved: true, result};
this.completed();
}

rejected(index, err) {
this.results[index] = {resolved: false, result: err};
this.completed();
}

completed() {
this.numAwaiting--;
if (this.numAwaiting === 0) this.deferred.resolve(this.results);
}
}

/**
* Waits for all promises in an array to settle (resolve or reject) and resolves to an array of
* the results.
* Each item in array is of form `{resolved: <boolean>, result: <any>}`.
* `resolved` is `true` if promise resolved, `false` if rejected.
* `result` is resolution value if resolved, rejection reason if rejected.
*
* @param {Array} promises - Array of promises
* @returns {Promise} - Promise of array of results
*/
module.exports = function settle(promises) {
const awaiter = new Settler();
return awaiter.run(promises);
};
88 changes: 88 additions & 0 deletions test/settle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* --------------------
* promise-methods module
* Tests for `settle` method
* ------------------*/

'use strict';

// Modules
const chai = require('chai'),
chaiAsPromised = require('chai-as-promised'),
{expect} = chai,
{settle} = require('../index');

// Init
chai.config.includeStack = true;
chai.use(chaiAsPromised);

// Tests

describe('settle()', () => {
it('resolves to array of result objects when all promise resolve', () => {
const p = settle([Promise.resolve(123), Promise.resolve(456)]);
return expect(p).to.eventually.deep.equal([
{resolved: true, result: 123},
{resolved: true, result: 456}
]);
});

it('awaits all promises', () => {
const p1 = Promise.resolve(123);
const p2 = new Promise(
resolve => setTimeout(() => resolve(456), 50)
);

const p = settle([p1, p2]);
return expect(p).to.eventually.deep.equal([
{resolved: true, result: 123},
{resolved: true, result: 456}
]);
});

it('returns rejection reason in results arr when a promise rejects', () => {
const err = new Error('e');
const p = settle([Promise.resolve(123), Promise.reject(err)]);
return expect(p).to.eventually.deep.equal([
{resolved: true, result: 123},
{resolved: false, result: err}
]);
});

it('returns rejection reasons in results arr when multiple promises reject', () => {
const err1 = new Error('e1'),
err2 = new Error('e2');
const p = settle([
Promise.resolve(123),
Promise.reject(err1),
Promise.reject(err2)
]);

return expect(p).to.eventually.deep.equal([
{resolved: true, result: 123},
{resolved: false, result: err1},
{resolved: false, result: err2}
]);
});

it('awaits all promises when one rejects', async () => {
const err = new Error('e');
const p = settle([
Promise.reject(err),
new Promise(
resolve => setTimeout(() => resolve(123), 50)
)
]);

return expect(p).to.eventually.deep.equal([
{resolved: false, result: err},
{resolved: true, result: 123}
]);
});

describe('with empty array', () => {
it('promise resolves to empty array', () => {
const p = settle([]);
return expect(p).to.eventually.deep.equal([]);
});
});
});

0 comments on commit 0a61214

Please sign in to comment.