According to the MDN documentation on await
:
When an await is encountered in code (either in an async function or in a module), the awaited expression is executed, while all code that depends on the expression's value is paused and pushed into the microtask queue.
async function foo(name) {
console.log(name, "start");
console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// First end
// Second start
// Second middle
// Second end
In this case, the two async functions are synchronous in effect, because they don't contain any await expression. The three statements happen in the same tick.
This is clear BUT this:
However, as soon as there's one await, the function becomes asynchronous, and execution of following statements is deferred to the NEXT tick.
async function foo(name) {
console.log(name, "start");
await console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
My question is why NEXT tick? Isn't it the same tick of the event loop? Microtask (all code that depends on the expression's value) will be taken from the microtask queue but NOT on the next tick?
My question is why NEXT tick?
The definition that Mozilla Contributors use for "tick" is slightly different from what the authors of the NodeJs documentation call "tick", and this might be the cause of confusion.
Isn't it the same tick of the event loop?
It is indeed executed in the same iteration of the event loop.
Microtask (all code that depends on the expression's value) will be taken from the microtask queue but NOT on the next tick?
then
-callbacks (or their await
function-resuming counterparts) are indeed taken from the microtask queue. Whether or not this is regarded as the same or different tick, depends on the definition of "tick".
This is what the NodeJs documentation defines as one phase of the event loop:
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘ Each box will be referred to as a "phase" of the event loop.
In the same document, a single iteration of this loop is identified as a "tick":
setImmediate()
fires on the following iteration or 'tick' of the event loop
This means in one tick (iteration), there can be many scheduled callbacks that are executed, including callbacks in the microtask queue.
Mozilla Contributors define "event loop" in a more generic and simplistic way here, and when discussing microtasks they say something similar to the NodeJs documentation:
Each agent is driven by an event loop, which is repeatedly processed. During each iteration, it runs at most one pending JavaScript task, then any pending microtasks, then performs any needed rendering and painting before looping again.
This reflects the HTML Standard that stipulates that one iteration of the event loop will pick and execute one task, and will then process any pending microtasks. If there are more tasks, then they will be treated in the next iterations of the event loop, one task at a time.
The major difference comes with what they call a "tick", as seen in what you quoted, and also here:
myPromise .then((value) => `${value} and bar`) .then((value) => `${value} and bar again`) .then((value) => `${value} and again`) .then((value) => `${value} and again`) .then((value) => { console.log(value); }) .catch((err) => { console.error(err); });
Note: For faster execution, all synchronous actions should preferably be done within one handler, otherwise it would take several ticks to execute all handlers in sequence.
What is called a tick here, refers to the engine initiating a single (then-) callback from the microtask queue, and in general, calling a callback from any queue that is monitored by the engine. For the authors of the NodeJs documentation those callbacks are all made in the same "tick".
As if this is not confusing enough, NodeJs includes a function called process.nextTick()
to schedule the execution of a callback, and the corresponding queue of callbacks is actually processed within the same event loop iteration! So what's in a name...:
The
process.nextTick()
queue is always processed before the microtask queue within each turn of the Node.js event loop.
Your understanding of the process is correct, but the different definitions of the word "tick" by different authors is bringing confusion to the subject.
I would avoid the term all together.