问题
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:
- You create a rejected promise with reason
'a'
. - You chain
.then()
to it which creates a new promise and returns that into your variablec
. - 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). - 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 anundefined
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