Does JavaScript Promise.all have a callback that is fired when there are success AND failures [duplicate]

拈花ヽ惹草 提交于 2020-01-12 06:55:28

问题


Am I misunderstanding Promise.all? I have X promises in an array and i'm trying to aggregate the success/failure ratio of the array.

Here is what I think I know:

Promise.all takes an array of promises.

If all of the promises succeed then the .then callback is ran.

If one of the promises fail then the .catch callback is called and the argument passed in is the value of the single raised error.

There is no callback fired which is the result of all the promises if some succeed and some fail. I.e. it can't give you an array like (pseudo code) [success, fail, success, success] - like one would expect and one can find in many JS libraries (ajax, ember, etc).

It's like the .then is more like a .success, not a function that always runs after all the promises are fulfilled regardless of whether some succeeded or some failed. Why doesn't have a .when .finally .runThisShizNoMatterWhat?? Or am I missing something (very probable)?


回答1:


This is related to Bluebird Promise.all - multiple promises completed aggregating success and rejections, but that's Bluebird-specific. The core of the issue is that if you want to inspect whether something succeeded or failed, then you aren't really asking for the direct result of each promise. Instead, you'd want to transform the promises before using Promise.all. There is no helper for this ES6-standard promises, but it is trivial to implement. In most libraries, this is known as Promise.settle. For example

var someThings = [...]; // some list of promises that may succeed or fail
settle(someThings).then(results => {
  results.forEach(result => {
    if (result.state === 'fullfilled'){
      console.log('succeeded', result.value);
    } else {
      console.log('failed', result.value);
    }
  });
});


function settle(arr){
  return Promise.all(arr.map(promise => {
    return promise.then(
      value => ({state: 'fullfilled', value}),
      value => ({state: 'rejected', value})
    );
  }));
}



回答2:


If one of the promises rejects, then the promise returned by Promise.all is rejected. So the rejection handler will be called right after one of the promises rejection. This might not be the desired behaviour if you just want to run all Promises without worrying about rejections (i.e. don't reject the promise if any promise rejects).

You can still handle each individual promise rejection so it will fulfill after the rejection.

var promiseRejected = new Promise(function(resolve, reject){
  setTimeout(function(){
    reject('I was rejected');
    }, 1000);
  });

promiseRejected = promiseRejected.then(null, function(reason){
  //I was rejected, now lets fullfill it.
  return reason;
  });

var promiseResolved = new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve('All good');
    }, 1500);
  });

var time = performance.now();

Promise.all([promiseRejected, promiseResolved]).then(function(results){
  //both promises fulfilled; 1500 msecs passed
  console.log(results[0], results[1], performance.now() - time);
  });

A promise constructor that resolves when all promises have been resolved/rejected example:

Promise.when = function (arrPromises) {
    if (!Array.isArray(arrPromises)) {
        return new TypeError('Expecting an Array of Promises');
    }
    return new Promise(function (resolve, reject) {
        var len = arrPromises.length,
            values = [],
            settled = 0;

        function settle(value, index) {
            values[index] = value;
            settled++;
            if (len === settled) {
                resolve(values);
            }
        }
        if (len === 0) {
            resolve([]);
        } else {
            arrPromises.forEach(function (promise, index) {
                var handler = function (value) {
                    settle(value, index);
                };
                promise.then(handler, handler);
            });
        }
    });
}



回答3:


If you're fine telling errors apart from your values, then doing what you want is as simple as:

Promise.all(array.map(promise => promise.catch(error => error)))

var log = msg => div.innerHTML += "<p>" + msg + "</p>";

var a = () => Promise.resolve(1);
var b = () => Promise.reject("error");
var c = () => Promise.resolve(3);

Promise.all([a(), b(), c()].map(p => p.catch(e => e))).then(r => log(r));
<div id="div"></div>



回答4:


Promise.all creates a new promise that can only either be resolved or rejected as a whole. Maybe you can think of it as having the every array method semantics which comes back with false when the first element doesn't match the predicate.

The then function takes up to two arguments, the second being the rejected handler. In this sense it's more than just a success, it can actually handle all cases. The catch is only a convenience method, short for .then(undefined, function(reason) { ... }).

The promise API does not have what you need I'm afraid, you would have to implement it yourself.




回答5:


From your question it seems that you are expecting to settle all the promises, which method promise.all doesn't guarantee, only method promise.settle guarantees to settle each promise in the array.

If you want the result of promise.all, while also settling each promise, plus have a notification of which promise resolved or rejected, then method spex.batch is exactly what you need.

Example as copied from Batch Processing:

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.

And you can see from the method's protocol that you can pass in optional cb - callback parameter that will be telling you which of the promises resolved and which ones rejected.




回答6:


I agree that using Bluebird.reflect to implement settle is the best way, if you are using Bluebird. Here is another solution that may be good depending upon your use case. It worked for me in a somewhat interesting situation. This also assumes Bluebird as the promise library.

In my case, I have an array of functions ("tasks") which are all wrapped in Promise.method. These tasks may return a rejected promise or they may return or throw plain values that will become promise resolutions. I needed to execute all of these and collect all results (not just first failure).

Again, bear in mind that each item in the tasks array is a Promise.method wrapped function. (Easy to implement. See http://bluebirdjs.com/docs/api/promise.method.html)

var runTasks = function(tasks) {
     return Bluebird.reduce(tasks, function (results, task) {
          return task()
            .then(function (result) {
              results.success.push(result)
              return results
            })
            .caught(function (result) {
              results.fail.push(result)
              return results
            })
     }, { success: [], fail: [] })
}

You would then call it like this, getting back an object that has an array of fulfilled values and an array of rejected values:

runTasks(taskArray)
  .then(function(results){
    // do whatever you want here
    // results.success is an array of the returned/resolved values
    // results.fail is an array of the rejected/thrown values
  })


来源:https://stackoverflow.com/questions/32978838/does-javascript-promise-all-have-a-callback-that-is-fired-when-there-are-success

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!