Event Loop Overview of the Event Loop

JavaScript is single threaded, because single thread will cause I/O blocking, for example, no response when sending a request may cause page stagnation, to solve this problem, browsers began to support asynchronous JS, asynchronous JS is to put some asynchronous tasks (Ajax, timer) into the task queue, The asynchronous code in the task queue is then read and triggered through an Event Loop, called an Event Loop.

An Event is an Event, such as a file read or timer. Loop means loop, that is, polling is used to execute events. To execute events, there must be sequence. The whole Event loop is to manage the execution sequence of events.

The core code of Event Loop is written in c++ (it belongs to NodeJs category). In essence, Event Loop is constantly reading and executing events in polling mode. Today we will discuss the details of Event Loop.

phase

The Event Loop is divided into the following stages

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code

Each of the above phases has a queue (fifO) containing the callback function. Whenever an Event Loop reaches a stage, it will generally execute some function in the queue (or not)

An overview of the stages

  • Timers phase: This phase executes the callback functions of setTimeout and setInterval.
  • I/O Callbacks phase: Callbacks that are not executed in the Timers, Close Callbacks, and Check phases are responsible for this phase, which contains almost all callback functions.
  • Idle, prepare stage: The stage used internally by the event loop (we don’t care about this stage)
  • Poll phase: Obtains new I/O events. Node.js blocks at this stage in some scenarios.
  • Check phase: Execute the setImmediate() callback function.
  • Close Callbacks phase: Executes the callback function that closes the event, such as fn in socket.on(‘close’, fn).

When a Node.js program terminates, Node.js checks to see if the Event loop is waiting for an asynchronous I/O operation to end and a timer to fire. If not, it closes the Event loop.

timers

This stage is most likely the first stage of the Event Loop, mainly storing macro tasks such as setTimeout or setInterval

poll

This phase is used to fetch new I/O events. When the Event Loop enters the poll phase and finds that the poll queue is empty, the Event Loop checks the latest timer with about 100 milliseconds left. Therefore, the Event Loop decides to stop in the poll phase during this period. When the timer task is about to start, the Event Loop bypasses the poll phase and enters the Check phase

check

NodeJS setImmediate Mediate An API that is often used during interviews, setImmediate, is also a macro task, but features faster execution than timers

SetImmediate and setTimeout

SetImmediate is similar to setTimeout, but the timing of its callback function is different.

SetImmediate Is the Check phase, while setTimeout is timers. What does setImmediate do first?

Let’s take a look at some code

setTimeout(()=>{console.log('timeout')},0)
setImmediate(()=>{console.log('immediate')},0)
Copy the code

In general, it takes precedencesetTmmediateBut the code above is actually executed in this orderIf the value of setTimeout is set to 1000ms, the immediate check phase exists. If the value of setTimeout is set to 1000ms, the immediate check phase must be executed immediately. The Event Loop is in the poll phase, and after all, it takes 1000ms to execute functions in the Timers queue, so the Loop plans to take a break.

Then, as if time were running out, the Loop found the check immediate function and ran to it. After executing it, it ran to timers.

The biggest reason for the confusion above is that the timer is set to 0, depending on the stage at which the Event Loop starts.

If the Event Loop is in the Timers phase and no timer task exists in the queue or the timer task is not running, the Event Loop skips this phase and executes the immediate task first.

If there is a task and the time is up, setTimeout must be executed first, which is why the above code is confusing.

So let’s rewrite it

setTimeout(() = > {
  setTimeout(() = > {
    console.log("timeout");
  }, 0);

  setImmediate(() = > {
    console.log("immediate");
  });
}, 1000);
Copy the code

The above code, after 1 second, executes the arrow function. At this time, the Event Loop is not in the Timers stage. Since the order is immutable, it is always executed firstimmediate

process.nextTick()

You may notice that process.nexttick (), the important asynchronous API, does not appear at any stage because process.Nexttick () is not technically part of the Event loop. In fact, regardless of which stage the Event Loop is currently in, the nextTick queue is executed after the current stage.

setTimeout(() = > {
  setTimeout(() = > {
    console.log("timeout");
  }, 0);

  setImmediate(() = > {
    console.log("immediate");
  });
  
  process.nextTick(() = >{
     console.log('nexTick')})},1000);
Copy the code

The above code is executed in this order

NextTick is executed immediately in the current phase. Since the Loop is in the poll phase after the above code is executed, nextTick will be executed first

Let’s change the code again just to make it a little bit more experimental

setTimeout(() = > {
  setTimeout(() = > {
    console.log("timeout");
    process.nextTick(() = > {
      console.log("nexTick2");
    });
  }, 0);

  setImmediate(() = > {
    console.log("immediate");
  });

  process.nextTick(() = > {
    console.log("nexTick");
  });
}, 1000);
Copy the code

Here is the result, and you can see that nextTick is executed immediately in the current phase

nexTick
immediate
timeout
nexTick2
Copy the code

Process. NextTick () and setImmediate ()

The two functions are similar in function and have confusing names.

The callback to process.nexttick () is executed “immediately” during the current Event loop phase. The setImmediate() callback is executed in subsequent Event Loop cycles (tick).

The names should be interchangeable. Process.nexttick () is more immediate than setImmediate().

This is a relic of history and is unlikely to change for backward compatibility. So while the names may sound confusing, they’re unlikely to change in the future.

We recommend that developers use setImmediate() in all situations, as it is more compatible and easier to understand.

Macro and micro tasks

Asynchronous tasks are divided into macro tasks and micro tasks, and micro tasks are always executed before macro tasks

Common macro tasks

Common microtasks

Classic Interview questions

setTimeout(() = > console.log(4))/ / macro task

new Promise(resolve= > {
  resolve()// Synchronize tasks
  console.log(1) // Synchronize tasks
}).then(() = > {
  console.log(3) Micro / / task
})

console.log(2) // Synchronize tasks
Copy the code

Converted to await

setTimeout(_= > console.log(4)) / / macro task

async function main() {
  console.log(1) // Synchronize tasks
  await Promise.resolve() // Synchronize task equivalent to resolve()
  console.log(3) // equivalent to promise.then // microtasks
}
main()
console.log(2) // Synchronize tasks
Copy the code