Using native Javascript Promise:
Promise.resolve(1).then(it => console.log(it)) console.log(2)
This is logged:
2 1
Question: how is it possible for 2
to execute before 1
? JS being event-driven, what is the event that is executing the callback given to then
when the original caller has already left that execution tree? Is the engine doing some kind of behind-the-scene magic here?
Advertisement
Answer
JavaScript maintains something called a callstack. This is used to keep track of whereabouts in the script we are. When you call a function, it gets added to the callstack, and when the function returns/finishes, it gets removed/popped off the callstack. It is also helpful to think of your entire script also as being in its own “function”, and so, when your script first begins its executing, we add “script” to the callstack:
Stack: - Script
When your Promise resolves, it executes its associated .then()
method and adds the callback to something called the micro-task queue. This queue (along with the macro-task queue) is used so that JavaScript can manage asynchronous events. As a result, once you run:
Promise.resolve(1).then(it => console.log(it))
the state of your queues/stacks looks like so (note, this is the state after Promise.resolve(1) and .then() have been added/popped off the callstack):
Stack: - Script Micro task queue: - it => console.log(it)
The callback in the micro-task queue will only execute once it gets added to the Stack. This happens through the use of the event-loop. The event-loop will pull tasks off the micro-task queue only when the callstack is empty. Currently, we are still running “script”, so the callstack isn’t empty yet.
Next, you encounter your console.log(2)
, as this is a function call, it gets added to the call stack, and once it returns it gets popped off. This is the reason why you see 2
in the console appear first, as the Promise’s .then()
callback hasn’t executed yet, as it’s sitting in the micro-task queue waiting for the main script to finish. One the main script finishes, “script” gets popped off the stack:
Stack: - (empty) <----------------< | --- gets moved to the stack by the event-loop Micro task queue: | - it => console.log(it) ---^
the event loop then moves the task(s) from the micro-task queue now that the callstack is empty. Once the task is moved to the callstack, it executes, running the second console.log(it)
. This is why you see 1
logged in the console after the 2
.