skip to Main Content

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


  1. 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:

    async function foo(name) {
        console.log(name, "start");
        const promise = Promise.resolve(console.log(name, "middle"));
        console.log(promise);
        await promise;
        console.log(name, "end"); // executed in 2 separate microtasks
    }
    
    foo("First");
    foo("Second");

    enter image description here

    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:

        // use a resolved Promise to postpone the move as a microtask so
        // the order of state mutation isn't important
        ops[prop] && Promise.resolve().then(ops[prop]);
    

    Is there a design pattern for logic performed on multiple event listeners

    Login or Signup to reply.
  2. 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 as part of the same task that was initiated from 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".

    References for different definitions

    This is what the NodeJs documentation defines as one phase of the event loop:

    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.

    This is a different concept from what Mozilla Contributers define as a tick 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.

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search