.finally are always asynchronous.
Even when a Promise is immediately resolved, the code on the lines below your
.finally will still execute first.
Here’s the code that demonstrates it:
If you run it, you see
code finished first, and then
That’s strange, because the promise is definitely done from the beginning.
.then triggered after? What’s going on?
Asynchronous tasks need proper management. For that, the standard specifies an internal queue
PromiseJobs, more often referred to as “microtask queue” (v8 term).
As said 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 that simply, when a promise is ready, its
That’s why “code finished” in the example above shows first.
Promise handlers always go through that 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, and executed when the current code is complete and previously queued handlers are finished.
What if the order matters for us? How to make
code finished work after
Easy, just put it into the queue with
Now the order is as intended.
“Event loop” is a process when the engine sleeps and waits for events, then reacts on those and sleeps again.
Examples of events:
mousemove, a user moved their mouse.
setTimeouthandler is to be called.
- an external
<script src="...">is loaded, ready to be executed.
- a network operation, e.g.
Things happen – the engine handles them – and waits for more to happen (while sleeping and consuming close to zero CPU).
As you can see, there’s also a queue here. A so-called “macrotask queue” (v8 term).
When an event happens, and the engine is busy, the event is enqueued.
For instance, while the engine is busy processing a network
fetch, a user may move their mouse causing
setTimeout may be due and so on, just as painted on the picture above.
Events from the macrotask queue are processed on “first came – first served” basis. When the engine browser finishes with
fetch, it handles
mousemove event, then
setTimeout handler, and so on.
So far, quite simple, right? The engine is busy, so other tasks queue up.
Now the important stuff.
Microtask queue has a higher priority than the macrotask queue.
In other words, the engine first executes all microtasks, and then takes a macrotask. Promise handling always has the priority.
For instance, take a look:
What’s the order?
codeshows first, it’s a regular synchronous call.
promiseshows second, because
.thenpasses through the microtask queue, runs after the current code.
timeoutshows last, because it’s a macrotask.
It may happen that while handling a macrotask, new promises are created.
Or, vise-versa, a microtask schedules a macrotask (e.g.
For instance, here
.then schedules a
promise shows up first, because
setTimeout macrotask awaits in the less-priority macrotask queue.
As a side effect, macrotasks are handled only when promises give the engine a “free time”.
So call have a promise chain that doesn’t wait for anything, then things like
setTimeout or event handlers can never get in the middle.
Remember “unhandled rejection” event from the chapter Error handling with promises?
Now, with the understanding of microtasks, we can formalize it.
"Unhandled rejection" is when a promise error is not handled at the end of the microtask queue.
For instance, consider this code:
We create a rejected
promise and do not handle the error. So we have the “unhandled rejection” event (printed in browser console too).
We wouldn’t have it if we added
.catch, like this:
Now let’s say, we’ll be catching the error, but after an extremely small delay:
Now the unhandled rejction appears again. Why? Because
unhandledrejection triggers when the microtask queue is complete. The engine examines promises and, if any of them is in “rejected” state, then the event is generated.
In the example above
setTimeout adds the
.catch, and it triggers too, of course it does, but later, after the event has already occured.
Promise handling is always asynchronous, as all promise actions pass through the internal “promise jobs” queue, also called “microtask queue” (v8 term).
.then/catch/finallyis called after the current code is finished.
If we need to guarantee that a piece of code is executed after
.then/catch/finally, it’s best to add it into a chained
There’s also a “macrotask queue” that keeps various events, network operation results,
setTimeout-scheduled calls, and so on. These are also called “macrotasks” (v8 term).
The engine uses the macrotask queue to handle them in the appearance order.
Macrotasks run after the code is finished and after the microtask queue is empty.
In other words, they have lower priority.
So the order is: regular code, then promise handling, then everything else, like events etc.