23

I'm using babel to transpile my node.js@0.10.x code and I'm stuck with promises.

I need allSettled-type functionality that I could use in q and bluebird or angular.$q for example.

On babel's core-js Promise, there is no allSettled method.

Currently I'm using q.allSettled as a workaround:

import { allSettled } from 'q';

Is there something like that in babel polyfill? Alternatively, which is a good algorithm for me to try to implement?

Leonid Beschastny
  • 50,364
  • 10
  • 118
  • 122
Zlatko
  • 18,936
  • 14
  • 70
  • 123

7 Answers7

44

2019 Answer

There was a proposal to add this function to the ECMAScript standard, and it has been accepted! Check out the Promise.allSettled docs for details.

Original Answer

If you take a look at the implementation of q.allSettled you'll see it's actually quite simple to implement. Here's how you might implement it using ES6 Promises:

function allSettled(promises) {
    let wrappedPromises = promises.map(p => Promise.resolve(p)
        .then(
            val => ({ status: 'fulfilled', value: val }),
            err => ({ status: 'rejected', reason: err })));
    return Promise.all(wrappedPromises);
}
Michael Kropat
  • 14,557
  • 12
  • 70
  • 91
  • Michael - I'm looking for a solution like this as well (Promise.all that executes all the promises and then capture all the errors in an array) - in order to use your allSettled function, would I do an npm install q and then do const q = require('q'); in my application? – Roger Dodger Mar 01 '19 at 00:06
  • 1
    @RogerDodger The code I wrote is assuming you're running an ES6 capable runtime, so basically any current browser or modern version of Node. Or you're using a promise polyfill. If either is the case, then you don't need to import Q or any other promise library. – Michael Kropat Mar 01 '19 at 20:27
  • Darn, in Feb 2020 Edge still doesn't support `Promise.allSettled`, thanks for the polyfill from your original answer! – Big Money Feb 07 '20 at 21:26
  • Why wrapping a promise in `Promise.resolve(p)`? to be sure that it is definitely a promise? – OlehZiniak Nov 25 '20 at 16:42
  • @OlehZiniak Yes, exactly. Could be convenient if you have a mixed array of promises and non-promise values. Another option would be to have allSettled throw an error in that case. – Michael Kropat Nov 25 '20 at 18:02
14

2020 answer:

What the other answers are trying to do is to implement Promise.allSettled themselves. This was already done by the core-js project.

What you need to do is to make babel polyfill Promise.allSettled for you via core-js. The way you configure it to do so is through @babel/preset-env like so:

presets: [
    ['@babel/preset-env', {
        useBuiltIns: 'usage',
        corejs: {version: 3, proposals: true},
    }],
],

In your build artifact this will add a call to require("core-js/modules/esnext.promise.all-settled") which monkeypatches the .allSettled function to the promises API.

Ivan Rubinson
  • 3,001
  • 4
  • 19
  • 48
5
const allSettled = promises =>
  Promise.all(promises.map(promise => promise
    .then(value => ({ state: 'fulfilled', value }))
    .catch(reason => ({ state: 'rejected', reason }))
  ));

Or if you insist on polyfilling it:

if (Promise && !Promise.allSettled) {
  Promise.allSettled = function (promises) {
    return Promise.all(promises.map(function (promise) {
      return promise.then(function (value) {
        return { state: 'fulfilled', value: value };
      }).catch(function (reason) {
        return { state: 'rejected', reason: reason };
      });
    }));
  };
}

Taken from here

Marko Bonaci
  • 5,622
  • 2
  • 34
  • 55
  • 1
    This is pretty good (and I'm using it) but there's one bug in this polyfill. The property returned [should be `status` and not `state`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled). I issued a pull request here with a modified version of the polyfill which is still backward compatible (but have an ideal version which is missing the `state` field in the PR, too): https://github.com/robhicks/es2015-Promise.allSettled/pull/1 – patricknelson May 12 '21 at 04:12
  • 1
    @chunk_split thanks, I upgraded `core.js` to `v3` so now I have native `allSettled` and don't need the polyfill any more. – Marko Bonaci May 14 '21 at 09:39
2

Alternatively, which is a good algorithm for me to try to implement?

  1. create a new promise with an executor function
  2. use a counter/result array in the scope of the executor
  3. register a then() callback with each parent promise saving the results in the array
  4. resolve/reject promise from step 1 when counter indicates that all parent promises are done
the8472
  • 40,999
  • 5
  • 70
  • 122
  • Nice, though for now I'll stick to using Q (which is probably doing the same). – Zlatko Jun 01 '15 at 13:32
  • With regard to your step 4, how would I ever know to reject the new promise? –  Aug 19 '16 at 05:27
  • @torazaburo you're right, you would never reject, that's the point of allSettled :) Maybe some timeout, but that's a different use case and not reflecting what q.allSettled does. – Zlatko Aug 19 '16 at 08:03
1

Here's my attempt at something similar, I have Newsletter service and in my case I wanted my allSettled promise to resolve with an array of all the results (rejections and resolutions), IN ORDER, once all the email_promises are settled (all the emails had gone out):

Newsletter.prototype.allSettled = function(email_promises) {
    var allSettledPromise = new Promise(function(resolve, reject) {
        // Keep Count
        var counter = email_promises.length;

        // Keep Individual Results in Order
        var settlements = [];
        settlements[counter - 1] = undefined;

        function checkResolve() {
            counter--;
            if (counter == 0) {
                resolve(settlements);
            }
        }

        function recordResolution(index, data) {
            settlements[index] = {
                success: true,
                data: data
            };
            checkResolve();
        }

        function recordRejection(index, error) {
            settlements[index] = {
                success: false,
                error: error
            };
            checkResolve();
        }

        // Attach to all promises in array
        email_promises.forEach(function(email_promise, index) {
            email_promise.then(recordResolution.bind(null, index))
                .catch(recordRejection.bind(null, index));
        });
    });
    return allSettledPromise;
}
stujo
  • 2,089
  • 24
  • 29
1

my implementation will be below

Promise.prototype.myAllSettled = function (arr = []) {
  return new Promise(function processIterable(resolve, reject) {
    let result = [];
    arr.forEach((item) => {
      item
        .then((value) => {
          result.push({ status: "fulfilled", value: value });
          if (arr.length === result.length) resolve(result);
        })
        .catch((err) => {
          result.push({ status: "rejected", reason: `${err}` });
          if (arr.length === result.length) resolve(result);
        });
    });
  });
};
  • Hi Vikas, this is a bit old question. Right now you don't even need a library for that, it's supported natively in all major browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled – Zlatko Jun 03 '20 at 06:48
  • but not in electron – johnsimer Sep 09 '20 at 19:15
  • This has the issue that it reorders the promises based on the one that is completed first, this is not how the native function behaves – Ferrybig Sep 23 '20 at 12:22
0

Here's another take at the same functionality: spex.batch

The source code would be too much to re-post here, so here's just an example from the batch processing of how to use it:

var spex = require('spex')(Promise);

// function that returns a promise;
function getWord() {
    return Promise.resolve("World");
}

// function that returns a value;
function getExcl() {
    return '!';
}

// function that returns another function;
function nested() {
    return getExcl;
}

var values = [
    123,
    "Hello",
    getWord,
    Promise.resolve(nested)
];

spex.batch(values)
    .then(function (data) {
        console.log("DATA:", data);
    }, function (reason) {
        console.log("REASON:", reason);
    });

This outputs:

DATA: [ 123, 'Hello', 'World', '!' ]

Now let's make it fail by changing getWord to this:

function getWord() {
    return Promise.reject("World");
}

Now the output is:

REASON: [ { success: true, result: 123 },
  { success: true, result: 'Hello' },
  { success: false, result: 'World' },
  { success: true, result: '!' } ]

i.e. the entire array is settled, reporting index-bound results.

And if instead of reporting the entire reason we call getErrors():

console.log("REASON:", reason.getErrors());

then the output will be:

REASON: [ 'World' ]

This is just to simplify quick access to the list of errors that occurred.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138