I am new to Javascript and trying to understand the execution engine of JS. I know that any async code statement moves to the call-stack and then gets immediately removed and executed in a separate Web API thread (started by browser). And then the result is stored in a callback queue and those values are picked up by the event loop once the call stack is empty.
Can anyone confirm the order in which below mentioned statements will be moved to call stack ?
Promise.resolve(function1)
.then(function2)
.then(function3)
.then(function4);
console.log("Hello");
Does the whole promise chain move to call stack or individual then
s ?
Does the whole promise chain move to the call stack or individual
then
s ?
Not the whole chain. Also be careful how you express things here. The individual then
s are executed synchronously. It is the callback to a then
that is executed asynchronously. I suppose you refer to those callbacks.
The chained then
methods are executed on different promise objects, and each will queue a reaction handler in the Promise Job Queue,
but only at the time the corresponding promise resolves.
So we have at least 4 promise objects that are created synchronously here. The call to Promise.resolve
creates the first one,
which is immediately resolved. All three then
methods are executed as well, creating 3 pending promises.
Lets call these 4 promise objects A, B, C and D. So A is resolved, the others are pending.
As promise A is resolved, an entry is put on the Promise Job Queue. Let's call that H(A) ("Handler for reacting to resolved promise A")
This all happens synchronously. After the synchronous script finally executes the console.log
, the call stack is empty and the Promise Job Queue is processed:
The Promise Job Queue has H(A). It is pulled from that queue and put on the call stack.
This triggers the first then
callback, i.e. function2
.
Its return value is used to resolve promise B.
We should consider here the case where function2
returns a promise E (could be a thenable), but let's assume first that it is just returning a non-thenable.
Promise B is resolved with that value, and a new entry H(B) is put on the Promise Job Queue.
The callstack is empty again.
The Promise Job Queue is processed again, which now has H(B), ... and so it continues.
This will all happen in one single task if function2
, function3
and function4
return non-thenables, but the Job Queue will not have H(A), H(B), H(C), H(D) at the same time.
The queue will in this scenario only have one of them at a time.
More realistic is when a function like function2
returns the result of a call to an asynchronous API, such as fetch
.
In that case, promise B will be made dependent on the promise E that function2
returns. This dependency includes an asynchronous call to the then
method of E.
Without going into too much detail about that process, the essence is that the Promise Job Queue may not get an entry H(E) immediately.
This will only happen when promise E resolves. And when it does, the call stack will get H(E) from the Job Queue.
Its execution will involve a call to H(B), because of the dependency.
Because of this delay, the current Task will find at some point that the Job Queue is empty, and the task ends.
It will be then for the Event Loop to detect when there is something ( like H(E) ) on the Job Queue.
executed in a separate Web API thread
There is no separate Web API thread for executing such asynchronous code. All this happens in one thread.
That being said, there can be APIs involved that have non-JS parts, for instance accessing OS functions, which may execute via other (non-JS) threads. Also there is the concept of
Web Workers which get their own execution thread. But that has nothing to do with Promises.
the result is stored in a callback queue
It is not the result of asynchronous code execution that is stored in a callback queue. The Promise API stores a Promise Reaction Record in the queue at the time the promise is resolved. In case of Promise.resolve()
, this actually happens as part of the synchronous script execution.
those values are picked up by the event loop once the call stack is empty.
There is a precision to make here: there are different queues. The Promise Job Queue has priority over other queues, and so for instance user I/O events will not be processed as long as there are entries on the Promise Job Queue. The execution of these entries are considered part of the same Task.