Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens if I reject a Promise with another Promise value?

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?

like image 886
Jeremy Avatar asked Aug 29 '16 02:08

Jeremy


People also ask

What happens when a Promise is rejected in Promise all?

2) Rejected promises example The Promise. all() returns a Promise that is rejected if any of the input promises are rejected. In this example, we have three promises: the first one is resolved after 1 second, the second is rejected after 2 seconds, and the third one is resolved after 3 seconds.

Does rejecting a Promise return?

The Promise. reject() method is used to return a rejected Promise object with a given reason for rejection. It is used for debugging purposes and selective error catching.

Can a Promise be rejected twice?

Show activity on this post. Since promises can only resolve once (to either fulfilled or rejected), the first resolution wins and any further calls will be ignored. From the docs: In all cases where a promise is resolved (i.e. either fulfilled or rejected), the resolution is permanent and cannot be reset.

What happens on Promise reject?

The Promise. reject() method returns a Promise object that is rejected with a given reason.


2 Answers

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.

like image 163
Jeremy Avatar answered Nov 16 '22 22:11

Jeremy


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.

like image 43
Benjamin Gruenbaum Avatar answered Nov 16 '22 22:11

Benjamin Gruenbaum