When I call this promise, the output does not match with the sequence of function calls. The .then
comes before the .catch
, even though the promise with .then
was being called after. What is the reason for that?
const verifier = (a, b) =>
new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));
verifier(3, 4)
.then((response) => console.log("response: ", response))
.catch((error) => console.log("error: ", error));
verifier(5, 4)
.then((response) => console.log("response: ", response))
.catch((error) => console.log("error: ", error));
output
node promises.js
response: true
error: false
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.
A Promise is in one of these states: pending: initial state, neither fulfilled nor rejected. fulfilled: meaning that the operation was completed successfully. rejected: meaning that the operation failed.
Often Promise. all() is thought of as running in parallel, but this isn't the case. Parallel means that you do many things at the same time on multiple threads. However, Javascript is single threaded with one call stack and one memory heap.
The Promise.all() method is one of the promise concurrency methods. It can be useful for aggregating the results of multiple promises.
This is kind of a cool question to get to the bottom of.
When you do this:
verifier(3,4).then(...)
that returns a new promise which requires another cycle back to the event loop before that newly rejected promise can run the .catch()
handler that follows. That extra cycle gives the next sequence:
verifier(5,4).then(...)
a chance to run its .then()
handler before the previous line's .catch()
because it was already in the queue before the .catch()
handler from the first one gets in the queue and items are run from the queue in FIFO order.
Note, that if you use the .then(f1, f2)
form in place of the .then().catch()
, it does run when you expect it to because there's no additional promise and thus no additional tick involved:
const verifier = (a, b) =>
new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));
verifier(3, 4)
.then((response) => console.log("response (3,4): ", response),
(error) => console.log("error (3,4): ", error)
);
verifier(5, 4)
.then((response) => console.log("response (5,4): ", response))
.catch((error) => console.log("error (5,4): ", error));
Note, I also labeled all the messages so you can see which verifier()
call they come from which makes it a lot easier to read the output.
ES6 Spec on promise callback ordering and more detailed explanation
The ES6 spec tells us that promise "jobs" (as it calls a callback from a .then()
or .catch()
) are run in FIFO order based on when they are inserted into the job queue. It doesn't specifically name FIFO, but it specifies that new jobs are inserted at the end of the queue and jobs are run from the beginning of the queue. That implements FIFO ordering.
PerformPromiseThen (which executes the callback from .then()
) will lead to EnqueueJob which is how the resolve or reject handler gets scheduled to be actually run. EnqueueJob specifies that the pending job is added at the back of the job queue. Then the NextJob operation pulls the item from the front of the queue. This ensures FIFO order in servicing jobs from the Promise job queue.
So, in the example in the original question, we get the callbacks for the verifier(3,4)
promise and the verifier(5,4)
promise inserted into the job queue in the order they were run because both those original promises are done. Then, when the interpreter gets back to the event loop, it first picks up the verifier(3,4)
job. That promise is rejected and there's no callback for that in the verifier(3,4).then(...)
. So, what it does is reject the promise that verifier(3,4).then(...)
returned and that causes the verifier(3,4).then(...).catch(...)
handler to be inserted into the jobQueue.
Then, it goes back to the event loop and the next job it pulls from the jobQueue is the verifier(5, 4)
job. That has a resolved promise and a resolve handler so it calls that handler. This causes the response (5,4):
output to be shown.
Then, it goes back to the event loop and the next job it pulls from the jobQueue is the verifier(3,4).then(...).catch(...)
job where it runs that and this causes the error (3,4)
output to be shown.
It's because the .catch()
in the 1st chain is one promise level deeper in its chain than the .then()
in the 2nd chain that causes the ordering you reported. And, it's because promise chains are traversed from one level to the next via the job queue in FIFO order, not synchronously.
General Recommendation About Relying on This Level of Scheduling Detail
FYI, in general, I try to write code that does not depend upon this level of detailed timing knowledge. While it's curious and occasionally useful to understand, it is fragile code as a simple seemingly innocuous change to the code can lead to a change in the relative timing. So, if timing is critical between two chains like this, then I would rather write the code in a way that forces the timing the way I want it rather than rely on this level of detailed understanding.
Promise.resolve()
.then(() => console.log('a1'))
.then(() => console.log('a2'))
.then(() => console.log('a3'))
Promise.resolve()
.then(() => console.log('b1'))
.then(() => console.log('b2'))
.then(() => console.log('b3'))
Instead of output a1, a2, a3, b1, b2, b3 you will see a1, b1, a2, b2, a3, b3 because of the same reason - every then returns a promise and it goes to the end of the event-loop queue. So we can see this "promise race". The same is when there are some nested promises.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With