The first function creates a Promise object but doesn't call it until we call the function. By comparison, the second statement creates a Promise object and call it immediately. Am I right?
The first function is just a function. As long as it is not called no promise is created. As soon as it is called, there is not much of a difference with the direct execution you put as alternative.
I think when we call the timeout function, it firstly creates an asynchronous function setTimeout
setTimeout
is not created, the function exists as a built-in function. You can say it is called.
... and pushes it at the tail of the event queue.
It is not the function setTimeout
itself that is pushed unto the queue. It is the callback (resolve
in this case), including arguments, timeout period and the unique timer ID that is put in the list of active timers.
After all synchronous events finished, it will be called and creates a Promise object.
The Promise object is created during the call of the timeout
function.
The callback that is passed to new Promise
is usually called the promise constructor callback, it is at the moment that new Promise
is executed that also that callback is executed (synchronously) and the promise is created.
When synchronous code has finished, i.e. when the call stack is empty, the microtask queue is consumed first. At this stage there is nothing there, so then the task queue is verified. If by that time an active timer has expired, there will be an entry for it in the task queue.
The Promise object will also be pushed at the tail of the event queue, which is after other synchronous events
The promise object is not put in any queue. It is when the timer expires that the queued event will be triggered as a new task, i.e. the resolve
function will be called, which in turn will resolve the promise, which in turn will put entries in the microtask queue, one for each then
callback and await
-related effect. The microtask queue will be processed within the same (macro) task, before any other tasks are pulled from the task queue.
So there seems to be two event loops, am I right?
At least two; there can be more. For instance, in a browser context there may be yet another queue for events concerning DOM element mutation.
See alse this answer about what some of the different specs say about this.
- Are these two statements equal?
Practically yes; Promise.resolve()
is short for new Promise(r => r())
Why is the output sequence like the following?
setTimeout
involves a task queue, while .then
involves a microtask queue, which is always consumed before the task queue is processed; at least that is the consensus in current implementations.
Addendum
Here is some clarification for the sequence of events of the following code:
function timeout(ms){
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
First the code is parsed and a task is created to execute it:
Task 1
- The function
timeout
is defined (a hoisted variable with the function as value)
timeout
is called with argument 100
- The promise object is created and the constructor callback is called synchronously. The two arguments are provided by the system. They are internal functions for us to call when the promise should resolve into either a fulfilled or rejected state (that is for us to decide).
setTimeout
is called.
- The arguments passed to
setTimeout
are used to create an entry in the internal timer list.
- The timer's ID is returned, but your code does not use the return value.
- The created promise object is returned to the caller of
timeout
- The caller calls the
then
method of this promise, passing it an anynomous function (the one with console.log
)
- The promise internal implementation stores this function in a queue (not the event queue) for potential later execution
- The end of the code is reached, the callstack is empty
- The micro task queue is checked for any entries, but it is also empty
In the background:
- The timer expires and an entry is pushed to the task queue. It is a pending call to the function that was passed to
setTimeout
, i.e. resolve
in our case. How exactly this happens is implementation dependent.
The essence is that at some point the task queue has this task that can be consumed.
Task 2
- The entry in the task queue (to call
resolve
) is found and processed
resolve
is called
- The promise internals (that implemented this
resolve
function) mark the promise as resolved
- The promise internals create entries in the microtask queue: one for each
then
callback (or await
) it had stored in its internal queue.
In this case there is only one such entry: the anonymous function that was passed to the only then
method in the code.
- The call stack is empty (it was only a call to
resolve
)
- The micro task queue is checked for any entries, and there is one:
Task 2, Micro task 1
- The single entry in the micro task queue is consumed
- The anonymous function is called
console.log
is executed
- The
console
implementation produces output
- The callstack is empty
- There are no more entries in the micro task queue
There are no more entries in the task queue. The system continues to check for new entries in the task queue.