We all know that in browsers js is single threaded due to DOM manipulation. In order to make use of the computing power of multi-core CPU, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and cannot operate DOM. So, this new standard doesn’t change the single-threaded nature of JavaScript. Multithreading does not mean fast because it takes time to switch time slices. Because of the single thread, the event loop is born. Because NodeJS is based on JS, both rely on event loops to handle asynchronous events, but both have the same and different event loops. Before we look at the event loop mechanism of both, let’s understand two concepts about asynchronous tasks:

Microtasks are typically tasks that are executed immediately after the execution of the current task (that is, always before the macro task), such as tasks that need to be responded to in a series of tasks, or tasks that need to be executed asynchronously without assigning a new task to reduce the performance overhead. A MicroTask queue is an independent queue from a task queue. Microtask tasks are executed after each task is completed. Microtasks generated in each task will be added to the microTask queue. Microtasks generated in the microTask will be added to the end of the current queue, and the MicroTask will process all tasks in the queue in sequence.

NextTick implementation in VUE: Vue internally tries to use the native setImmediate Promise.then and MessageChannel(older versions use mutationObserver, deprecated due to compatibility issues) for asynchronous queues, and if the current implementation environment does not support it, Use setTimeout(fn, 0) instead.
// MutationObserver example:let observe = new MutationObserver(function () {
        console.log('DOM is all tucked in.');
    });
    // 也是一个微任务
    observe.observe(div,{childList:true});
    for (let i = 0; i < 100; i++) {
      let p = document.createElement('p');
      div.appendChild(p);
    }
    console.log(1);
    let img = document.createElement('p');
    div.appendChild(img);
Copy the code
// MessageChannel usage: console.log(1);let channel = new MessageChannel();
    let port1 = channel.port1;
    letport2 = channel.port2; // The asynchronous code vue is the macro task port1.postmessage ('hello');
    port2.onmessage = function (e) {
      console.log(e.data);
    }
    console.log(2);

    // 1 2 hello
Copy the code
// index.html js console.log(1); // index.html js console.log(1);let worker = new Worker('./worker.js'); worker.postMessage(1000); Worker.onmessage = worker.onmessage =function(e) {// Receive messages console.log(e.data); // Data in messages} console.log(2); // worker.js onmessage =function (e) {
  let sum = 0;
  for(var i = 0; i<e.data; i++){ sum += i; } this.postMessage(sum) }Copy the code

To understand the differences between the browser and Node event loops, run the code:

// Test the following code in the browser and Node respectively:setTimeout(() => {
  console.log('1')
  Promise.resolve('123').then(data => {
    console.log(2)
  });
});
setTimeout(() => {
  console.log('3'); }); // In the browser 1, 2, 3 // in the node environment: Node 11 and later always output 1, 2, 3, as in the browser. Node 10 and below have both 1, 2, 3 and 1, 3, 2 outputs, but 1, 3, 2 outputs are more frequent.Copy the code

Principle: Browser: execute the micro task after the content in the execution stack is executed, and then execute the macro task after the micro task is cleared. The macro task that reaches the condition will be executed in the stack eventually, and the event loop will be repeated continuously. Therefore, the above code will execute the Promise microtask first after printing 1, and then execute the task queue. Node: Microtasks are always executed before the start of a new event loop, so execute all setTimeout of arrival times first, and then Promise. Resolve before entering the next event loop. The following node event loop can help you understand:

Node Startup Process

  • 1. Call the platformInit method to initialize the nodeJS runtime environment.
  • 2. Call performance_node_start to perform performance statistics on nodeJS.
  • 3. Judgment of OpenSSL Settings.
  • V8_platform. Initialize Initialize the libuv thread pool.
  • 5. Call V8::Initialize to Initialize the V8 environment.
  • 6. Create a nodeJS running instance.
  • 7. Start the instance created in the previous step.
  • 8, start to execute THE JS file, synchronization code after the completion of execution, into the event cycle.
  • 9. Destroy the NodeJS instance when there are no listening events, and the program is finished.

There is such an event loop mechanism inside Libuv, which is an asynchronous library implemented in C and owes much of nodeJS ‘efficient asynchronous programming model to Libuv. The event loop is initialized when node starts

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers (timer) │ | | is carried outsetThe Timeout and | | |setInterval's callback. | │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ micro task │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks | │ | processing network, flow, TCP error | | | callback | │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ micro task │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ | | | use within the node │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ micro task │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ poll (polling) │ │ Incoming: │ | | execution poll in the I/o queue | < ─ ─ ─ ─ ─ ┤ connections, │ | | check whether the timer is then | │ data, etc. Read the file | │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ micro task │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ check (check) │ | | depositsetImmediate correction | │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ micro task │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ┤ close callbacks | │ closed callbacks such as | | sockect.on('close') | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ here every stage corresponds to an event queue, when the event loop to perform to a certain stage will be the current stage corresponds to the queue in sequence. When the queue completes or the number of executions exceeds the upper limit, it moves to the next phase. Microtasks are always performed at the beginning of a new cycle.Copy the code

Combined with the above flow chart, the principle of node event loop can be summarized:

  • Initialization of node

    • Initialize the Node environment.
    • Execute the input code.
    • Execute the process.nextTick callback.
    • Perform microtasks.
  • Enter the event loop

    • Entering the timers Stage

      • Check whether the timer queue has expired timer callbacks. If so, the expired timer callbacks are executed in ascending timerId order. Node 10 and below: Clears all timer callbacks before performing the check microtask below. Node 11 and above: Check the microtask queue every time a timer callback is executed, and execute it all. Then proceed to the next timer, same as the browser.
      • Check whether there are process.nextTick tasks. If so, execute them all.
      • Check whether microTasks exist. If yes, run them all.
      • Exit this phase.
    • The IO Callbacks phase is displayed.

      • Check for pending I/O callbacks. If so, perform the callback. If not, exit the phase.
      • Check whether there are process.nextTick tasks. If so, execute them all.
      • Check whether microTasks exist. If yes, run them all.
      • Exit this phase.
    • The system enters idle and prepare.

      • These two stages have little to do with our programming.
    • Enter the poll phase

      • First check to see if there are any outstanding callbacks, and if so, in one of two cases.
        • The first case:
          • Execute all available callbacks if any are available (including expired timers and IO events, etc.).
          • Check for process.nextTick callbacks, and if so, execute them all.
          • Check whether microtaks exist. If so, perform all operations.
          • Exit this phase.
        • The second case:
          • If no callback is available.
          • Check whether the immediate callback exists. If so, exit the poll phase. If not, block at this stage, waiting for new event notifications.
      • Exit the poll phase if there are no outstanding callbacks.
    • Enter the check phase.

      • If there are immediate callbacks, all immediate callbacks are executed.
      • Check for process.nextTick callbacks, and if so, execute them all.
      • Check whether microtaks exist. If so, perform all operations.
      • Exit the Check phase
    • Enter closing phase.

      • If there are immediate callbacks, all immediate callbacks are executed.
      • Check for process.nextTick callbacks, and if so, execute them all.
      • Check whether microtaks exist. If so, perform all operations.
      • Exit closing stage
    • Check if there are active handles (timer, IO, and other event handles).

      • If so, proceed to the next cycle.
      • If not, end the event loop and exit the program.

Before each subphase of the event loop exits, the following process is performed in sequence: check for process.NextTICK callbacks, and if so, execute them all. Check whether microtaks exist. If so, perform all operations. Exit the current phase.

Here are some possible interview questions to help you understand:

// Execute the following code under nodesetImmediate(function(){
console.log('1');
});
setTimeout(function(){
console.log('2'); }); // Due to node's preparation time, the output order is not necessarily the same.Copy the code
// But with a slight change:let fs =require('fs')
fs.readFile('./1.txt'.'utf8', () = > {setImmediate(function(){
      console.log('1');
    });
    setTimeout(function(){
      console.log('2'); }); }) // 1 2 the result is always 1 2, even thoughsetThe Timeout onsetIn front of the Immediate. Because polling is donesetThe Immediate. whilesetTimeout must be executed in a new loopCopy the code

// run the following command under node:

process.nextTick(function A() {
   console.log(1);
   process.nextTick(function B(){console.log(2); }); });setTimeout(function timeout() {
   console.log('TIMEOUT FIRED'); }, 0) // 1 2 TIMEOUT FIRED; No matter how many process.nextTick statements there are (whether they are nested or not), they will all be present"Execution stack"Execute after execution. That is, microtasks are always executed before a new cycle of events.Copy the code

// run the following command under node:

setImmediate(function () {
  console.log('4')})setImmediate(function () {
  console.log('5')
})
process.nextTick(function () {
  console.log('1')
  process.nextTick(function () {
    console.log('2')
    process.nextTick(function () {
      console.log('3')
    })
  })
})

console.log('next'// Always print next 1, 2, 3, 4, 5. After the synchronous code completes, the microtask is performed and the next event loop is enteredCopy the code

Conclusion:

Asynchronous tasks are divided into micro tasks and macro tasks

  1. The browser: macro task mainly setTimeout and setInterval, setImmediate (ie), messageChannel, ajax requests, click event, etc. Micro tasks mainly has Promise. Then, mutationObserver. Execute the contents inside the stack first, execute the micro task after the stack execution, and then read the asynchronous queue, and read the execution condition into the execution stack. And so on.
  2. Node side: macro task mainly setTimeout and setInterval, setImmediate, read and write files, etc. Micro tasks mainly has Promise. Then, the process nextTick (faster than Promise. Then execute).

Reference: the mechanism of JS event loop (the event loop) of the micro macro task, task: segmentfault.com/a/119000001… In-depth understanding of JavaScript event loops (2) – Task and MicroTask: www.cnblogs.com/dong-xu/p/7… Dissect the NodeJS event loop :juejin.cn/post/684490… From browser multi-process to JS single thread, JS operation mechanism is the most comprehensive combing :juejin.cn/post/684490…

Nguyen other node cycle: www.ruanyifeng.com/blog/2018/0…