最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - What happens if I reject a Promise with another Promise value? - Stack Overflow

programmeradmin0浏览0评论

If a Promise p is resolved with the value of a Promise (or Thenable) q, it essentially becomes a copy of Promise q. If q is resolved, p will be resolved with the same value.

Promise.resolve(Promise.resolve("hello"));
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello"}

If q is rejected, p will be rejected with the same value.

Promise.resolve(Promise.reject(new Error("goodbye")));
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Error: goodbye}

The fact that Promise p was resolved/rejected through Promise q, instead of directly with the respective value, is irrelevant to the final result. The intermediate Promise is consumed as part of the resolution process, and is not visible to the consumer.

If q is a never resolved or rejected, p will also remain pending forever.

Promise.resolve(new Promise(() => null)); // perpetually-pending promise
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

These cases are well-known, but I have never seen what happens if a Promise is rejected (instead of resolved) with another Promise value. Does the rejection process also consume intermediate Promises, or are they passed through intact?

If it does consume them, how does that work?

If a Promise p is resolved with the value of a Promise (or Thenable) q, it essentially becomes a copy of Promise q. If q is resolved, p will be resolved with the same value.

Promise.resolve(Promise.resolve("hello"));
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello"}

If q is rejected, p will be rejected with the same value.

Promise.resolve(Promise.reject(new Error("goodbye")));
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Error: goodbye}

The fact that Promise p was resolved/rejected through Promise q, instead of directly with the respective value, is irrelevant to the final result. The intermediate Promise is consumed as part of the resolution process, and is not visible to the consumer.

If q is a never resolved or rejected, p will also remain pending forever.

Promise.resolve(new Promise(() => null)); // perpetually-pending promise
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

These cases are well-known, but I have never seen what happens if a Promise is rejected (instead of resolved) with another Promise value. Does the rejection process also consume intermediate Promises, or are they passed through intact?

If it does consume them, how does that work?

Share Improve this question edited Sep 13, 2017 at 12:55 user5393970 asked Aug 29, 2016 at 2:31 Jeremy Banks - Vive le CanadaJeremy Banks - Vive le Canada 130k88 gold badges358 silver badges381 bronze badges 8
  • This isn't difficult to test yourself, but I was trying to quickly look up this behaviour the other day and couldn't find the answer in my searches, so I decided to make a post. – Jeremy Banks - Vive le Canada Commented Aug 29, 2016 at 2:33
  • Not certain what Question is? – guest271314 Commented Aug 29, 2016 at 2:37
  • 1 @guest271314: No. There are three sentences with a question mark in the post. Read them. There is no issue, he is just trying to understand what happens. – Bergi Commented Aug 29, 2016 at 2:48
  • 3 "I have never seen what happens" - for good reason. A Promise.reject message should always be wrapped in an Error. – Bergi Commented Aug 29, 2016 at 2:50
  • 1 "If q is resolved, p will be resolved with the same value." is a trap! It should read "If q is fulfilled, p will be fulfilled with the same value". – traktor Commented Aug 29, 2016 at 5:28
 |  Show 3 more comments

4 Answers 4

Reset to default 10

Let's see what happens if we reject a Promise p with a resolved Promise q:

Promise.reject(Promise.resolve("hello"));
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}
Uncaught (in promise) Promise {
    [[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello"}

Or more explicitly:

const q = Promise.resolve("hello");
const p = Promise.reject(q);
p.then(null, x => console.log("Rejection value:", x));
Rejection value: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello"}

The Promise q, the rejection value, is never unwrapped! p's rejection handlers are called with the the Promise q itself, not the value it contains.

This also means that p's rejection handler doesn't need to wait for q to be resolved before it can run. Even if q is never resolved, p's rejection handler can still be called.

Promise.reject(new Promise(() => null)); // Reject with perpetually-pending Promise
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}
Uncaught (in promise) Promise {
    [[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

Finally, let's confirm the behaviour if we reject Promise p using another a rejected Promise q:

Promise.reject(Promise.reject(new Error("goodbye")));
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}
Uncaught (in promise) Error: goodbye(…)(anonymous function)
Uncaught (in promise) Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Error: goodbye}

We see again that q is not unwrapped, and p's rejection handler will be called with q itself, not the value that q has been rejected with.

So, Jeremy's answer explains what happens:

const p = Promise.reject(Promise.resolve(3)); 

p is a rejected promise with the rejection value of a Promise of 3.

We were taught to believe promises never resolve with promises! Well, this is a special case. Here, we are rejecting a promise with another promise in contradiction to what then does.

But why?!?

Easy there sport. Let's first get some terminology down.

A promise starts of as pending, it can either become:

  • fulfilled - marking it completed with a value.
  • rejected - marking it failed with a reason.

So far so good, but let's consider two additional terms:

  • resolved - meaning it resolved to another promise value and is tracking it.
  • settled - meaning it's actually fulfilled or rejected - either through the promise it's following resolving or on its own.

Phew. Now that that's out of the way:

What Promise.resolve does is create a promise resolved to another value. If that value is a promise it tracks it - otherwise it settles immediately with the value passed in. This is also what happens if you return from within a then or await something in an async function.

What Promise.reject does is create a promise rejected with another value. It has no chance to follow another promise as it is immediately created with a rejected result.

This behavior is specified in reject and resolve. In particular - we're creating a promise capability and resolve is special - namely look at "Promise Resolve Functions".

Ok, you told me what happens - but why?!?!?!?

Well, let's consider the alternatives. We want resolve to mimic returning from a then or awaiting in an async function and reject to mimic throwing in a then or in an async function.

const p = Promise.resolve().then(() => {
    throw Promise.reject(5);
});

It is clearer to see resolving p to 5 makes no sense! We'd mark the promise as completed correctly but it clearly did not complete correctly.

Similarly:

async function foo() { throw Promise.resolve(5); } foo(); // no one would expect foo to resolve.

What about rejecting with the unwrapped value?

That would mean we lose the information about which rejection we're dealing with. The only alternative is to reject with a Promise object.

Should I ever run into this?

No, never. You should never throw promises anyway and you should always reject with Errors.

Promise object construction:

    new Promise( executor)

calls the executor function with two call back function arguments:

   executor( resolve, reject)

where resolve is overloaded by argument type to either

  1. link the promise to a thenable:

    resolve( thenable);  // link the resolved promise to the final state of the thenable.
    

    where the resolved promise remains pending until the promise it is linked to ("was resolved with") becomes settled, or

  2. fulfill the promise with something that is not a thenable

      resolve (notThenable);  // fulfill the promise with anything except a thenable.
    

Being javascript, "overloaded" in this context is performed by examination of the argument type and properties by resolve at run time, not when the script is compiled. A simplified explanation is that you can't fulfill a promise with a promise or promise like object.

The reject function is not overloaded and does not examine its argument at run time. If a promise is rejected with a promise or other thenable it is treated as any other rejection reason. The promise does not remain pending and becomes rejected. The promise used for rejection is passed as the reason argument to any functions catching promise rejection. A simplified explanation is that you can reject a promise with anything you like, but if it is not an Error object or descriptive reason you are on your own!

Promise.resolve() can take a value, a thenable or a promise. It adapts its behaviour.

Promise.reject() takes only an immediate value. So if you pass a Promise to it, it will clumsily try to treat it as an immediate value.

However, you do not consume the promise by passing it to Promise.reject. You can do this:

Promise.reject(myPromise); // weird and useless, and with no side effect
myPromise.then(e=>{console.log(e)}); // "consume" the promise
myPromise.then(e=>{console.log(e)}); // you can consume it as many times as you want
发布评论

评论列表(0)

  1. 暂无评论