First, why should there be an event loop?

JS is single-threaded, non-blocking. The main purpose of JS is to interact with the user and manipulate the DOM. If multiple threads are involved, one thread will delete the current DOM node and another thread will modify the current DOM node, which will cause serious synchronization problems. This is why JS is designed to be single-threaded, and its non-blocking nature is implemented by event loops.

Second, the browser event loop

Execution stack and event queue

Synchronizes code and adds it to the execution stack in order

The following code is used as an example to analyze the execution order of the execution stack.

function a() {
    b();
    console.log('a');
}
function b() {
    console.log('b')
}
a();
Copy the code
  1. Function A is pushed.
  2. Function B is pushed.
  3. The console. The log (‘ b ‘) into the stack.
  4. Output b, console.log(‘b’) out of the stack.
  5. Function b() completes and exits the stack.
  6. Console. log(‘a’) is pushed into the stack, and then removed from the stack.
  7. Function A completes and exits the stack.

Asynchronous code, through the event queue

Asynchronous code execution, asynchronous events will not wait for it to return the result, but this event hangs, continue to perform other tasks in the execution stack, when an asynchronous event to return to the results, put it in the event queue, be in to the event queue of the statement is not immediately, but waiting for the currently executing all tasks are completed in the stack, the main thread is idle, The main thread asks if there is a task in the event queue summary, and if there is, it fetches the first event, puts the corresponding callback on the execution stack, and executes its synchronization code.

function a() {
    b();
    console.log('a');
}
function b() {
    console.log('b')
    setTimeout(function() {
        console.log('c');
    }, 2000)
}
a();
Copy the code

The output sequence is as follows: b A C

The relationship between execution stack, event queue, and Web API.

Macro and micro tasks

Why distinguish between macro and micro tasks?

Page rendering events and various IO events are added to the task queue at any time, and the principle of first-in, first-out will always be maintained. However, we cannot prioritize various events, because some tasks need to be executed first, so we distinguish asynchronous tasks by introducing macro tasks and micro task queues.

Typical macro tasks

  • Global code
  • Script (whole code)
  • setTimeout()
  • setInterval()
  • setImmediate
  • postMessage
  • I/O
  • UI interaction events
  • requestAnimationFrame()

Typical microtasks

  • Promise (). Then (the callback)
  • MutationObserver
  • process.nextTick

Operation mechanism

In the event loop, macro tasks first, because the global code belongs to macro, micro and macro tasks of the task, if the task in the process of execution creates new tasks, continues to perform tasks, after the completion of the task, macro mission, the current macro after the task has been completed, start GUI rendering, Proceed to the next macro task after rendering.

console.log('start')

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

Promise.resolve().then(function() {
  console.log('promise1')
}).then(function() {
  console.log('promise2')})console.log('end')
Copy the code

The above code is executed in the following order:

  1. The global code starts execution as a macro task, executes directly when encountering a synchronization task, and prints start.
  2. The function in setTimeout is pressed into the macro task queue, the promise.then callback is put into the microtask queue, and then the synchronous code is executed to print end.
  3. The promise callback is fulfilled and the promise1 output is promise1. Then undefined is returned. The promise state becomes depressing, triggering the subsequent then callback and generating a new microtask. Continue with the microtask, exporting promisE2.
  4. When the microtask queue completes, UI rendering begins, and then the next event loop begins, executing the setTimeout callback and printing setTimeout.

The final output is:

  • start
  • end
  • promise1
  • promise2
  • setTimeout

Browser render timing

The browser will wait until the current microtask queue is empty to re-render, so the best way to re-render the DOM after an asynchronous operation is to wrap it as a microtask.

Node.js event loop

The event loop in Node is very different from that in the browser. Node.js uses V8 as the PARSING engine of JS, and uses libuv of its own design for I/O processing. The Libuv library is responsible for executing the Node API, assigning different tasks to different threads, forming an event loop, and asynchronously returning the results of the tasks to the V8 engine.

Flowchart of the event loop in Node

The event loop in the Libuv engine is divided into six stages, which are executed sequentially and repeatedly. When a stage is entered, functions are removed from the corresponding callback queue and executed. When the queue is empty or the number of callback functions executed reaches the threshold set by the system, the next stage will be entered.

Phase analysis

  • Timers: Run the callback of setTimout and setInterval to check whether the delay requirements are met. If not, leave this phase.
  • I/O Callbacks phase: Perform all callbacks except the following.
    • Callbacks to setTimeout and setInterval
    • SetImmediate callback
    • The callback function used to close the request, such as socket.on(‘close’)
  • Idle,prepare: This stage is used only in libuv.
  • Poll phase: This phase is the polling phase, which first determines whether the poll queue is empty or not. If not, the callback queue is traversed and executed until the queue is empty or the system limit is reached. If the poll queue is not empty, check to see if any setImmediate mediate needs to be executed, and if so, end the poll phase and enter the Check phase. If the timer is set, the system checks whether the timer times out. If the timer times out, the system returns to the timer phase and executes the callback.
  • Check phase: This phase mainly performs the setImmediate callback function.
  • Close Callbacks phase: This phase executes the callback function that closes the request, such as socket.on(‘close’).

The execution order of setTimeout and setImmedate is incorrect

Note that setTimeout is set to 1 when the delay is greater than 2147483647 or less than 1. A non-integer delay is truncated to an integer.

Take a look at the following code: What is the order of execution?

setTimeout(() = > console.log(1),0);
setImmediate(() = > console.log(2));
Copy the code

The reason why the above situation occurs is that when entering the timer phase, it may reach 1ms or not. If it does not reach the timer phase, the statement will be skipped and the next phase will be entered. This is why the execution order will be different.

The execution timing of process.nextTick

Process.nexttick is an asynchronous execution function provided by Node.js. It is not an alias of setTimeout(fn,0), and is more efficient. It is executed before setTimeout and setInterval.

The order of execution of the following code says it all.

console.log(1);
setTimeout(() = > console.log('setTimeout=> 1'), 0);
process.nextTick(() = > console.log('nextTick=> 1'));
console.log(2);
setTimeout(() = > console.log('setTimeout=> 2'), 0);
process.nextTick(() = > {
  console.log('nextTick=> 2');
  for (let i = 0; i < 1000; i++) { }    // Wait for it to complete before executing the next nextTick() and subsequent callbacks in the task queue
});
console.log(3);
process.nextTick(() = > console.log('nextTick=> 3'));
setTimeout(() = > console.log('setTimeout=> 3'), 0);
console.log(4);
setTimeout(() = > console.log('setTimeout=> 4'), 0);
process.nextTick(() = > console.log('nextTick=> 4'));
console.log(5);

for (let i = 0; i < 1000; i++) { }        // Wait for it to complete before executing the nextTick and setTimeout callbacks.
Copy the code

Differences between Node.js and browser event loops

The following code illustrates the difference between node.js and the browser event loop. Specifically, Node11 and later versions are executed in the same order as browsers.

In Node11 and later versions, all macro tasks are completed before microtasks are executed. In Node11 and later versions, microtasks are completed before other macro tasks are executed.

function test () {
  console.log('start')
   setTimeout(() = > {
       console.log('children2')
       Promise.resolve().then(() = > {console.log('children2-1')})},0)
   setTimeout(() = > {
       console.log('children3')
       Promise.resolve().then(() = > {console.log('children3-1')})},0)
   Promise.resolve().then(() = > {console.log('children1')})
   console.log('end') 
}

test()
// The result of executing the above code below node11 (all macro tasks first, then micro tasks)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// The result of executing the above code on node11 and the browser (macro tasks and micro tasks in sequence)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
Copy the code

Analysis of typical topics

  • Netease Cloud examination questions
const foo = new Promise(resolve= > {
  console.log(1);
  resolve();
  console.log(2);
})
foo.then(() = > {
  console.log(3);
})
console.log(4);
Copy the code

Resolve does not block because 1, 2, and 3 are added to the microtask queue, so 3 and 4 are synchronized code.

  • Bytedance test questions
console.log(1);
setTimeout(() = > {
  console.log(2);
}, 0);
async function echo() {
  console.log(5);
  await Promise.resolve()
  console.log(6);
}

echo();

requestAnimationFrame(() = > {
  console.log(8);
})

new Promise((resolve) = > {
  console.log(3);
  resolve()
}).then(() = > {
  console.log(4);
})

console.log(7);
Copy the code

The output order of the above code is 1 5 3 7 6 4 8 2. We need to treat the requestAnimation as a macro task, but the order of execution of this macro task and other macro tasks is uncertain, so the output may be 1 5 3 7 6 4 2 8.

reference

  • The Event Loop mechanism in JavaScript