Promise.settle and promise fulfillment vs rejection

人盡茶涼 提交于 2020-01-02 09:55:41

问题


Consider the following code that contains a simplified implementation of Bluebird's Promise.settle:

var a = Promise.reject('a');
var b = Promise.resolve('b');
var c = Promise.resolve('c');

var promises = [a,b,c];

function settled(promises) {
  var alwaysFulfilled = promises.map(function (p) {
    return p.then(
      function onFulfilled(value) {
        return { state: 'fulfilled', value: value };
      },
      function onRejected(reason) {
        return { state: 'rejected', reason: reason };
      }
    );
  });
  return Promise.all(alwaysFulfilled);
}

//Update status message once all requests finish
settled(promises).then(function (outcomes) {
  var count = 0;
  outcomes.forEach(function (outcome) {
    if (outcome.state == 'fulfilled') count++;
  });

  console.log(count + ' out of ' + outcomes.length + ' balances were updated');
});

This will log "2 out of 3 balances were updated". Why does this work differently than a plain Promise.all? Shouldn't alwaysFulfilled still contain a rejected promise as its first element?

The answer seems to lie in my confusion over how promises work. If I create a rejected promise in the console and .then it like so:

var a = Promise.reject('a');
var b = a.then(function() {}, undefined);
var c = a.then(undefined, function() {});

a
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: "a"}
b
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: "a"}
c
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}

Why is c "resolved"?


回答1:


The key to understanding your question is that when you supply a reject handler in a .then() or with a .catch(), then you are telling the promise system that you're "handling" the rejection. So, unless your reject handler itself throws or returns a rejected promise itself, then the return value of that reject handler goes into a fulfilled promise, not a rejected promise. Much more on this explained below...

In fact, the inverse is true also. If you have a reject handler and based on the type of rejection, you want the rejection to continue to propagate back into a rejected promise, you have to either throw the error from the reject handler or return a rejected promise.

This will log "2 out of 3 balances were updated". Why does this work differently than a plain Promise.all?

Promise.all() returns a rejected promise as soon as it gets the first rejection in the list of promises given to it. It does not necessarily return all results and it returns rejected if any promise you passed it is rejected. It essentially gives up as soon as one promise is rejected. That's the point of Promise.settle(). It will gives you all the results, even if some are rejected and you can then cull through all the results.

Shouldn't alwaysFulfilled still contain a rejected promise as its first element?

As explained below, when you have a reject handler in a .then() and that reject handler does not throw or return a rejected promise (e.g. it returns a normal value like you are doing), then that promise rejection is considered handled and the resulting promise from the .then() handler is fulfilled, not rejected. More explanation in steps below...

The answer seems to lie in my confusion over how promises work. If I create a rejected promise in the console and .then it like so... Why is c "resolved"?

First off, .then() returns a new promise. So a.then() is not returning a. It's returning a new promise that is the product of what happens in the .then() handler.

When you do this:

var c = Promise.reject('a').then(undefined, function() {});

Here's what is happening:

  1. You create a rejected promise with reason 'a'.
  2. You chain .then() to it which creates a new promise and returns that into your variable c.
  3. Then, because the original promise was rejected, the second handler to .then() is called. When debugging or designing your code, remember this is always called asynchronously (this can confuse people in the debugger sometimes).
  4. You return undefined from that reject handler. At this point, the Promise system considers the rejection "handled" and the result of .then() is a fulfilled promise with an undefined value (that's what your handler returned).

If you want the result to still be rejected, then you can either throw or you can return a rejected promise from your reject handler. It is done this way so you can "handle" a rejection and keep the promise chain going successfully. Also, an unhandled rejection will result in a rejected promise, but having a reject handler tells the promise system that your code is taking care of the rejection and the resulting promise will be formed based on the return result from the reject handler.

So, all these would result in c being rejected:

// no reject handler
var c = a.then(function() {});

// throw from reject handler 
var c = a.then(undefined, function() { throw new Error("whatever")});    

// return rejected promise from reject handler
var c = a.then(undefined, function() { return Promise.reject("whatever")});   

But, if you have a reject handler and it doesn't either throw or return a rejected promise, then the rejection is considered "handled" and the resulting promise is resolved with whatever value your handler returns.



来源:https://stackoverflow.com/questions/32160055/promise-settle-and-promise-fulfillment-vs-rejection

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