Promise.allSettled in babel ES6 implementation

2020-05-26 09:40发布

问题:

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?

回答1:

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


回答2:

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);
}


回答3:

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;
}


回答4:

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.



回答5:

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.