.finally are always asynchronous.
Even when a Promise is immediately resolved, the code on the lines below
.finally will still execute before these handlers.
Here’s a demo:
If you run it, you see
code finished first, and then
That’s strange, because the promise is definitely done from the beginning.
Why did the
.then trigger afterwards? What’s going on?
Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue
PromiseJobs, more often referred to as the “microtask queue” (V8 term).
As stated in the specification:
- The queue is first-in-first-out: tasks enqueued first are run first.
- Execution of a task is initiated only when nothing else is running.
Or, to say more simply, when a promise is ready, its
That’s why “code finished” in the example above shows first.
Promise handlers always go through this internal queue.
If there’s a chain with multiple
.then/catch/finally, then every one of them is executed asynchronously. That is, it first gets queued, then executed when the current code is complete and previously queued handlers are finished.
What if the order matters for us? How can we make
code finished run after
Easy, just put it into the queue with
Now the order is as intended.
unhandledrejection event from the article Error handling with promises?
An “unhandled rejection” occurs when a promise error is not handled at the end of the microtask queue.
Normally, if we expect an error, we add
.catch to the promise chain to handle it:
But if we forget to add
.catch, then, after the microtask queue is empty, the engine triggers the event:
What if we handle the error later? Like this:
Now, if we run it, we’ll see
Promise Failed! first and then
If we didn’t know about the microtasks queue, we could wonder: “Why did
unhandledrejection handler run? We did catch and handle the error!”
But now we understand that
unhandledrejection is generated when the microtask queue is complete: the engine examines promises and, if any of them is in the “rejected” state, then the event triggers.
In the example above,
.catch added by
setTimeout also triggers. But it does so later, after
unhandledrejection has already occurred, so it doesn’t change anything.
Promise handling is always asynchronous, as all promise actions pass through the internal “promise jobs” queue, also called “microtask queue” (V8 term).
.then/catch/finally handlers are always called after the current code is finished.
If we need to guarantee that a piece of code is executed after
.then/catch/finally, we can add it into a chained