Event loop is difficult to understand, and the question of examining the execution sequence of event loop often appears in interviews. This paper explains event loop in detail.

The Event loop define

1. This is the W3C definition

To coordinate events, user interaction, scripts, rendering, networking, and so forth, In order to coordinate events, user interactions, scripting, rendering, networking, etc., user agents (i.e. browsers) must use the event loops described in this section. (: No official Chinese, translated by translation software)

2. This is the definition of MDN

JavaScript has a concurrency model based on event loops, which are responsible for executing code, collecting and processing events, and executing subtasks in queues

3. This is the definition of Node.js

Event loops are node.js’ mechanism for handling non-blocking I/O operations — even though JavaScript is single-threaded — and they move operations to the system kernel when possible.

Example 1:

// Answer the output order
setTimeout(() = > {
	console.log('setTimeout asynchronous')},0)
console.log('sync')
Copy the code

The answer:

Synchronous setTimeout AsynchronousCopy the code

If you answered the wrong question, please review the asynchronous JavaScript example 2:

// Answer the output order
setTimeout(() = > {
	console.log('setTimeout asynchronous')},0)
Promise.resolve().then(() = >{
    console.log('promise asynchronous')})console.log('sync')
Copy the code

The answer:

Synchronous PROMISE asynchronous setTimeout AsynchronousCopy the code

The promise asynchronous task code is written after the setTimeout asynchronous task code, but the result is that the Promise asynchronous task executes the setTimeout asynchronous task first and then executes it, which is controlled by the Event loop, which controls the order in which the asynchronous task callbacks are executed.

Task, microtask definition

The Event Loop divides asynchronous tasks into tasks (macrotask) and microtasks (macrotask).

task

Most asynchronous operations are tasks, such as: Parsing events (click, Mousemove, etc.), Fetch (network request), Parsing of HTML by the browser, Dom manipulation (Dom addition and reduction, etc.), setTimeout/setInterval, etc. Remember microtask, except microtask is task, microtask is easy to remember. When encountering a task during code execution, the browser pushes the task to a Task Queue. After all synchronized codes are executed, the browser executes the contents of the task quque in sequence until the Task Queue is empty. When the task queue is executing and empty, new tasks are pushed to a new task queue and wait for the next execution by the browser.

// Pseudo-code approximation
const taskQueue = [] // task queue Queue
// The task is added to the queue
taskQueue.push(() = > {console.log('task 1')})
taskQueue.push(() = > {console.log('task 2')})
taskQueue.push(() = > {console.log('task 3')})
while(taskQueue.length) {
  // Execute them one by one
	 taskQueue.shift()()     
}
Copy the code

Examples:

// Answer the output order
console.log('synchronous 1') // The synchronization task is executed directly
setTimeout(() = > {
	console.log('setTimeout asynchronous 1') // The first task in the task queue
}, 0)
setTimeout(() = > {
	console.log('setTimeout asynchronous 2') // Put the second task in the task queue
}, 0)
setTimeout(() = > {
	console.log('setTimeout asynchronous 3') // Put the third task in the task queue
}, 0)
console.log('synchronous 2') // The synchronization task is executed directly
// Now execute task queue!
Copy the code

The answer:

// Perform synchronization firstsynchronous1synchronous2
// Execute task queue tasks in sequence
setTimeoutasynchronous1
setTimeoutasynchronous2
setTimeoutasynchronous3
Copy the code

microtask

w3c:

A microtask is a task that is originally to be queued on the microtask queue rather than a task queue. A microtask is a task that would have been queued on a microtask queue rather than a task queue

mdn:

At first the differences between microtasks and tasks seem small. They’re very similar; Are made up of JavaScript code that sits on a queue and runs when appropriate. However, only tasks that are in the queue at the beginning of the iteration are run one after another by the event loop, which is quite different from working with microtask queues.

Microtask tasks are pushed into the MicroTask Queue, and callbacks in the MicroTask queue are executed after the synchronized code is executed. However, new microtasks generated during the execution of the MicroTask Queue will be pushed to the current microTask queue and will be executed last, without waiting for the next execution of the microTask queue. Examples:

Promise.resolve().then(() = > {
    console.log('promise1')})Promise.resolve().then(() = > {
    console.log('promise2')
}).then(() = > {
    console.log('promise4')})Promise.resolve().then(() = > {
    console.log('promise3')})console.log('synchronous 1')
Copy the code

The answer:

synchronous1
promise1
promise2
promise3
promise4
Copy the code

Resolution:

// The initial Microtask queue is empty []
/ * micro tasks:) = > {the console. The log (' promise1 ')} get-together, queue [() = > {the console. The log (' promise1 ')}] * /
Promise.resolve().then(() = > {
    console.log('promise1')})/ * micro tasks: (() = > {the console. The log (' promise2 ')}), then (() = > {the console. The log (' promise4 ')}) team, queue [() = > {the console. The log (' promise1)  }, (() => { console.log('promise2') }).then(() => { console.log('promise4') }) ] */
Promise.resolve().then(() = > {
    console.log('promise2')
}).then(() = > {
    console.log('promise4')})/ * micro tasks:) = > {the console. The log (' promise3 ')} get-together, queue [() = > {the console. The log (' promise1 ')}, (() => { console.log('promise2') }).then(() => { console.log('promise4') }), () => { console.log('promise3') } ] */
Promise.resolve().then(() = > {
    console.log('promise3')})// Execute console.log(' sync 1')
console.log('synchronous 1')
// Execute microtask queue
/* 
执行 console.log('promise1'),队列 [
    (() => {
        console.log('promise2')
    }).then(() => {
        console.log('promise4')
    }),
    () => {
         console.log('promise3')
    }
] */

/* Execute console.log('promise2'), microtask: () = > {the console. The log (' promise4 ')} get-together, queue [() = > {cnsole. Log (' promise3 ')}, () = > {the console. The log (' promise4 ')}] * /

/ * to perform the console. The log (' promise3), queue [() = > {the console. The log (' promise4 ')}] * /

/* Execute console.log('promise4'), queue [], microtask completed */

Copy the code

task vs micotask

mdn:

There are two key differences.

First, every time a task exists, the event loop checks to see if the task is ceding control to other JavaScript code. If not, the event loop runs all the microtasks in the microtask queue. The microtask loop is then processed multiple times in each iteration of the event loop, including after the event and other callbacks are processed.

Second, if a microtask adds more microtasks to the queue by calling queueMicrotask(), those newly added microtasks will run before the next task. This is because the event loop keeps calling microtasks until there are no remaining microtasks in the queue, even if more microtasks keep being added.

A task is any JavaScript that is scheduled to be executed by a standard mechanism, such as program initialization, event-triggered callbacks, and so on. In addition to using events, you can also use setTimeout() or setInterval() to add tasks. ** The difference between task queues and microtask queues is simple, but important:

  • When executing tasks from the task queue, each task in the queue is executed by the runtime at the beginning of each iteration of the new event loop. Tasks added to the queue at the beginning of each iteration will not be executed until the next iteration begins.
  • Every time a task exits and the execution context is empty, each microtask in the microtask queue is executed in turn. The difference is that it waits until the microtask queue is empty before stopping execution — even if a microtask joins in mid-stream. In other words, a microtask can add new microtasks to the queue and complete all of the microtasks before the next task starts and the current event loop ends.

The microtasks in the MicroTask queue are executed before each task in the task queue is executed, because the execution of a task may add new microtasks to the MicroTask queue. Examples:

setTimeout(() = > {
    console.log('setTimeout1')},0)

setTimeout(() = > {
    console.log('setTimeout2')
    Promise.resolve().then(() = > {
        console.log('promise2')})},0)

setTimeout(() = > {
    console.log('setTimeou3')},0)

Promise.resolve().then(() = > {
    console.log('promise1')})console.log('synchronous 1')
Copy the code

The answer:

synchronous1
promise1
setTimeout1
setTimeout2
promise2
setTimeou3
Copy the code

Resolution:

// Initial macro task queue []
// Initial microtask queue []
/* Macro task: () => {console.log('setTimeout1')}] macro task queue: [] */
setTimeout(() = > {
    console.log('setTimeout1')},0)
Resolve (). Then (() => {console.log('promise2')})} [ () => { console.log('setTimeout1') }, () = > {the console. The log (' setTimeout2) Promise. Resolve (). Then (() = > {the console. The log (' promise2 ')})}] micro task queue: [] * /
setTimeout(() = > {
    console.log('setTimeout2')
    Promise.resolve().then(() = > {
        console.log('promise2')})},0)
/* Macro task: () => {console.log('setTimeout3')} [ () => { console.log('setTimeout1') }, () => { console.log('setTimeout2') Promise.resolve().then(() => { console.log('promise2') }) }, () = > {the console. The log (' setTimeout3 ')}] micro task queue: [] * /
setTimeout(() = > {
    console.log('setTimeou3')},0)
/* Microtasks: () => {console.log('promise1')} [ () => { console.log('setTimeout1') }, () => { console.log('setTimeout2') Promise.resolve().then(() => { console.log('promise2') }) }, () = > {the console. The log (' setTimeout3 ')}] micro task queue: [() = > {the console. The log (' promise1 ')}] * /
Promise.resolve().then(() = > {
    console.log('promise1')})// Execute sync code console.log(' sync 1')
console.log('synchronous 1')
/* Task queue: [ () => { console.log('setTimeout1') }, () => { console.log('setTimeout2') Promise.resolve().then(() => { console.log('promise2') }) }, {console.log('setTimeout3')}] [() => {console.log('promise1')}] Microtask priority is higher, execute microtask queue before macro task execution: () => {console.log('promise1')} microtask queue: [] macro task queue: [ () => { console.log('setTimeout1') }, () => { console.log('setTimeout2') Promise.resolve().then(() => { console.log('promise2') }) }, () => { console.log('setTimeout3') }] */

() => {console.log('setTimeout1')} microtask queue: [] macro task queue: [ () => { console.log('setTimeout2') Promise.resolve().then(() => { console.log('promise2') }) }, () => { console.log('setTimeout3') }] */

/* The microtask has a higher priority, and the microtask queue is executed before the macro task is executed. The microtask queue is empty, and the macro task is executed. () => {console.log('setTimeout2') () = > {/ / console log (' promise2 ') / /} team micro task queue Promise. The resolve (). Then (() = > {the console. The log (' promise2 ')})}, micro task queue: [() = > {the console. The log (' promise2 ')}] macro task queue: [() = > {the console. The log (' setTimeout3 ')}] * /

{console.log('promise2')} {console.log('promise2')} [] macro task queue: [() = > {the console. The log (' setTimeout3 ')}] * /

Macro task: () => {console.log('setTimeout3')}, macro task queue: [] macro task queue: [] */
// All execution completed
Copy the code

The execution of micro task and macro task may generate new micro task and macro task, so first draw the queue of micro task and macro task respectively, and then join and leave the queue according to the execution, otherwise it is easy to make mistakes.

Problem sets

Promise problem sets

Promise should be careful to distinguish between synchronous and asynchronous code.

new Promise((resolve) = > {
    console.log('This is sync 1 for promise.')
    resolve()
    console.log('This is sync 2 for the promise.')
}).then(() = >{
    console.log('This is asynchronous 1 for promise.')})console.log('sync')
Copy the code

The answer:

This is the synchronization of the promise1This is the synchronization of the promise2Synchronization this is the asynchrony of promise1
Copy the code

Resolution:

// Do not see the promise as async, then is async, new promise declaration is synchronous!
new Promise((resolve) = > {
    // This is synchronized!
    console.log('This is sync 1 for promise.')
    // Then is asynchronous
    resolve()
    // This is synchronized!
    console.log('This is sync 2 for the promise.')
}).then(() = >{
    console.log('This is asynchronous 1 for promise.')})// Synchronize, but after the promise declaration!
console.log('sync')
Copy the code

Async problem sets

The lower left part of the await in async functions is equivalent to new Promise().then

/ * async () = > {XXX code It is also right -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | here is bottom left upper right const p = | | this is await XXX (); | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- here is also lower left XXX code} * /
Copy the code

Example 1:

const asyncFunction = async() = > {console.log('1' async synchronization)

    await new Promise((resolve) = > {
        console.log('This is sync 1 for promise.')
        resolve()
        console.log('This is sync 2 for the promise.')})console.log('async asynchronous 1')
}

asyncFunction()

console.log('synchronous 1')
Copy the code

The answer:

asyncsynchronous1This is the synchronization of the promise1This is the synchronization of the promise2synchronous1
asyncasynchronous1
Copy the code

Resolution:

// async function the first await is synchronous, followed by the equivalent of new promise.then () is a microtask
const asyncFunction =  () = > {
    console.log('1' async synchronization)

     new Promise((resolve) = > {
        console.log('This is sync 1 for promise.')
        resolve()
        console.log('This is sync 2 for the promise.')
    }).then(() = > {
        console.log('async asynchronous 1')
    })
}

asyncFunction()

console.log('synchronous 1')
Copy the code

Example 2:

const asyncFunction = async() = > {console.log('1' async synchronization)
    await new Promise((resolve) = > {
        console.log('This is sync 1 for promise.')
        resolve()
        console.log('This is sync 2 for the promise.')})console.log('async asynchronous 1')
    await new Promise((resolve) = > {
        resolve()
    })
    console.log('async asynchronous 2')}Promise.resolve().then(() = > {
    console.log('promise asynchronous 1')
})
asyncFunction()
Promise.resolve().then(() = > {
    console.log('promise asynchronous 2')})console.log('synchronous 1')
Copy the code

The answer:

asyncsynchronous1This is the synchronization of the promise1This is the synchronization of the promise2synchronous1Promise the asynchronous1
asyncasynchronous1Promise the asynchronous2
asyncasynchronous2
Copy the code

Resolution:

// Just replace await with promise inside async functions
const asyncFunction = () = > {
    console.log('1' async synchronization)
    new Promise((resolve) = > {
        console.log('This is sync 1 for promise.')
        resolve()
        console.log('This is sync 2 for the promise.')
        // The first await becomes.then
    }).then(() = > {
        console.log('async asynchronous 1')
        return new Promise((resolve) = > {
            resolve()
        })
        // The second await becomes.then
    }).then(() = > {
        console.log('async asynchronous 2')})}Promise.resolve().then(() = > {
    console.log('promise asynchronous 1')
})
asyncFunction()
Promise.resolve().then(() = > {
    console.log('promise asynchronous 2')})console.log('synchronous 1')
Copy the code

Comprehensive problem sets

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')}async function async2(){
    console.log('async2')}console.log('script start')
setTimeout(function(){
    console.log('setTimeout')},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')})console.log('script end')
Copy the code

The answer:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
Copy the code

Resolution:

// 1. Write async functions into promise form
function async1(){
    / / synchronize
    console.log('async1 start')
    new Promise((resolve) = > {
        // The resolve function executes synchronously
        resolve(async2())
        // then join the team micro task
    }).then(() = > {
        console.log('async1 end')})}function async2(){
    / / synchronize
    console.log('async2')}console.log('script start')
// Join the team macro task
setTimeout(function(){
    console.log('setTimeout')},0)  
async1();
new Promise(function(resolve){
    / / synchronize
    console.log('promise1')
    resolve();
    // Team micromission
}).then(function(){
    console.log('promise2')})console.log('script end')
Copy the code

conclusion

The issue of Event loop is very complicated, especially in the case of async. Due to space limitation, this paper cannot list all contents, so readers need to try to summarize. In addition, attach other events loop not explained in this paper, and readers can check the corresponding links: 1. SetImmediate and Process. nextTick Do you need microtasks