JS is known as a non-blocking single-threaded language because it was originally created to interact with browsers. If JS is a multithreaded language, we may have problems processing the DOM in multiple threads (adding nodes in one thread and removing nodes in the other), of course we can introduce read and write locks to solve this problem.

JS generates execution environments during execution, and these execution environments are sequentially added to the execution stack. If asynchronous code is encountered, it is suspended and added to the Task queue (there are multiple tasks). Once the execution stack is empty, the Event Loop takes the code that needs to be executed from the Task queue and puts it into the execution stack for execution, so it is essentially asynchronous or synchronous behavior in JS.

console.log('script start')

setTimeout(function() {
  console.log('setTimeout')},0)

console.log('script end')
Copy the code

The above code is asynchronous even though the setTimeout delay is 0. This is because the HTML5 standard states that the second argument to this function must be at least 4 milliseconds, which increases automatically. So setTimeout will still print after script end.

Different Task sources are assigned to different Task queues. Task sources can be divided into microtasks and macrotasks. In the ES6 specification, microtasks are called Jobs and MacroTasks are called tasks.

console.log('script start')

setTimeout(function() {
  console.log('setTimeout')},0)

new Promise(resolve= > {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')})console.log('script end')
// script start => Promise => script end => promise1 => promise2 => setTimeout
Copy the code

Although setTimeout is written before Promise in the above code, because Promise belongs to micro task and setTimeout belongs to macro task, there will be the above printing.

Microtasks include Process. nextTick, Promise, Object.observe, and MutationObserver

Macro tasks include Script, setTimeout, setInterval, setImmediate, I/O, and UI Rendering

A lot of people have a misconception that microtasks are faster than macro tasks, but that’s not true. Because a macro task includes script, the browser executes a macro task first, followed by a microtask if there is asynchronous code.

So the correct sequence for an Event loop is this

  1. Execute synchronized code, which is a macro task
  2. If the execution stack is empty, check whether microtasks need to be executed
  3. Perform all microtasks
  4. Render the UI if necessary
  5. Then start the next Event loop, which executes the asynchronous code in the macro task

As we can see from the Event loop sequence above, if the asynchronous code in the macro task has a lot of computation and needs to manipulate the DOM, we can put the DOM into the micro task for faster interface response.

Event loop in Node

The Event loop in Node is different from the Event loop in the browser.

The Node Event loop is divided into six stages, which are repeated in sequence

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

timer

The timers phase executes setTimeout and setInterval

The time specified by a timer is not the exact time, but the callback is executed as soon as possible after this time, which may be delayed because the system is executing another transaction.

The lower limit time has a range of [1, 2147483647]. If the specified time is not in this range, it will be set to 1.

I/O

The I/O phase performs all but the close event, timer, and setImmediate callbacks

idle, prepare

Idle, internal implementation in the prepare phase

poll

The poll phase is important, and in this phase, the system does two things

  1. The timer of the point-to-point is executed
  2. Execute events in the poll queue

And when there is no timer in poll, the following two things can be found

  • If the poll queue is not empty, the callback queue is traversed and executed synchronously until the queue is empty or system limited
  • If the poll queue is empty, two things happen
    • If you havesetImmediateThe poll phase stops and the check phase is executedsetImmediate
    • If there is nosetImmediateIf needed, it waits for the callback to be queued and executes it immediately

If another timer needs to be executed, the callback is returned to the timer phase.

check

The Check phase performs setImmediate

close callbacks

Close The Close event is executed during the Callbacks phase

In Node, timer execution is random in some cases

setTimeout(() = > {
  console.log('setTimeout')},0)
setImmediate(() = > {
  console.log('setImmediate')})SetTimeout, setImmediate
// May also reverse the output, depending on performance
// setImmediate is executed because it may take less than 1 millisecond to enter the Event loop
// Otherwise setTimeout will be executed
Copy the code

In this case, of course, the order of execution is the same

var fs = require('fs')

fs.readFile(__filename, () = > {
  setTimeout(() = > {
    console.log('timeout')},0)
  setImmediate(() = > {
    console.log('immediate')})})// Because the readFile callback is executed in poll
// setImmediate detects that there is no setImmediate, so it immediately jumps to the Check phase to perform the callback
// Execute setTimeout in the timer phase
// So the above output must be setImmediate
Copy the code

All of the above are macroTask executions, which are executed immediately after each of the above phases.

setTimeout(() = > {
  console.log('timer1')

  Promise.resolve().then(function() {
    console.log('promise1')})},0)

setTimeout(() = > {
  console.log('timer2')

  Promise.resolve().then(function() {
    console.log('promise2')})},0)

// The above code prints differently in the browser and node
// The browser must print timer1, promise1, timer2, promise2
// Node may print timer1, timer2, promise1, promise2
It is also possible to print timer1, promise1, timer2, promise2
Copy the code

Process. nextTick is executed before other microtasks in Node.

setTimeout(() = > {
  console.log('timer1')

  Promise.resolve().then(function() {
    console.log('promise1')})},0)

process.nextTick(() = > {
  console.log('nextTick')})// nextTick, timer1, promise1
Copy the code