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?
2
Answers
When JS encounters
await
it executes the statement immediately and if the statement returns a fullfilled promise or a value (which is treated as a resolved promise) creates a microtask pushing it to the end of the current task for executing the next statements.We could refactor the code to replace
console.log
with a fullfilled promise equivalent and console.log it:Interestingly enough but we can use microtasks to our advantage for example collecting sync input from the user and react to it in a microtask. Here I use that to collect mouse pointer data in any order and handle it later in a microtask.
That’s a really cool feature, it makes the UI looking sync, because there’s no new tasks created. On the other hand using
setTimeout
could give a less pleasant result with possible jittering of the UI since new tasks are created allowing the event loop fire any intermediate tasks from the UI/IO events that could even mutate our state used in our current UI work:Is there a design pattern for logic performed on multiple event listeners
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.
It is indeed executed as part of the same task that was initiated from the event loop.
then
-callbacks (or theirawait
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".References for different definitions
This is what the NodeJs documentation defines as one phase of the event loop:
The Event Loop:
In the same document, a single iteration of this loop is identified as a "tick":
This means in one tick (iteration), there can be many scheduled callbacks that are executed, including callbacks in the microtask queue.
This is a different concept from what Mozilla Contributers define as a tick in what you quoted, and also here:
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…:Conclusion
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.