Node.js visualized: promise, async/await and process.nextTick under the hood
The MDN definition for a promise is “the eventual completion (or failure) of an asynchronous operation.”
The definition feels like we are dealing with some form of obscure magic 🧙♂️, but, in simple terms, a
promise is a container for future value.
promise behaves to a certain extent like a browser containing either the desired page content or an error message. Whenever we navigate to an address, our web browser can be in three different states:
- A blank page or a loading spinner could be shown while fetching the data.
- The page content is successfully displayed.
- An error can appear.
Similarly, a promise can take three statuses:
pending,when for example, the query is not complete;
fulfilled,when the network request is complete;
rejected,when the asynchronous request has finished.
Hands-on example of promise status
await, but these two expressions are an alternative way to write promises.
Promises under the hood
Promises seem to be processed synchronously, but their execution is delayed to a specific point in the future.
The following script shows that
console.logs are run before the promise callback.
Under the hood, the V8 engine memorizes the callback function and pushes it to a microtask queue when the promise
Once the call stack is empty, V8 can process microtasks.
Promise microtask queue and the event loop
The microtask queue and the event loop wait for the call stack to be empty before pushing tasks for execution, but which one has the higher priority?
The following script shows that Node.js and its internals will always try to exhaust the microtask queue before handing control back to the event loop and its phases:
Promise.reject queue up a macrotask in the timer phase and a microtask.
The callback in the microtask queue is run before the event loop timer phase. Once there are no microtasks, the event loop can resume its execution.
setTimeout callback invokes
setImmediate and pushes another
promise microtask that will be executed at an empty call stack.
Is everything for microtasks?
process.nextTick also schedules microtasks that Node.js manages in a dedicated queue and have higher priority (in CommonJS) than the promise queue and the event loop phases.
Once the call stack is empty, Node.js processes the
process.nextTick microtasks, followed by the
promise queue and the event loop phases.
If you liked the article, follow us on Twitter @fabrizio.lallo and @AndrewHu368