JS basis - the event loop mechanism

A question from shallow to say JavaScript event loop

Original link: https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7

Note: This article runtime environment for the current version of Google Chrome (72.0.3626.109)

Task Queue

First, we need to understand the following things:

  • JS divided into synchronous and asynchronous tasks Tasks
  • Synchronization tasks are executed on the main thread to form a stack execution
  • Outside the main thread, the thread event-triggered manages a task queue, as long as the running asynchronous tasks with the results, it is placed in the event a task queue.
  • Once all of the simultaneous execution of tasks completed stack (JS engine idle at this time), the system reads the task queue will be added to the running asynchronous tasks executable stack, it started.

According to the specification, the event loop through the task queue to coordination mechanisms. Event Loop a, there may be one or more queues tasks (task queue), a queue task is a collection of ordered tasks (task); and each task has a source task (task source), derived from the same task a task source must be placed in the same task queue, to come from different sources were added to different queues. setTimeout / Promise and other API is the task source, into the task queue is their assigned specific tasks.

Task Queue

Macro task

(Macro) task (also known as macro task), it is understood that each execution of the code stack is a macro to perform tasks (including each get an event from the event queue callback execution stack and placed in execution).

In order to be able to make internal browser JS (macro) task and DOM tasks to be executed sequentially, will be at the end of a (macro) task execution, the next (macro) task to perform before the start of the page is re-rendered , the process is as follows:

(macro)task->渲染->(macro)task->...

(Macro) task mainly includes: script (the whole Code), setTimeout, setInterval, I / O, UI interaction events, postMessage, MessageChannel, setImmediate (Node.js environment)

Micro-task

microtask (also known as micro-tasks), can be understood tasks performed immediately after the end of the current task execution . That is, before the current task after task, the next task, before rendering.

So its response speed compared to setTimeout (setTimeout is a task) will be faster, because without waiting for rendering. That is, in a certain macrotask executed, all microtask will be generated during its execution were completed (before rendering).

microtask mainly includes: Promise.then, MutaionObserver, process.nextTick (Node.js environment)

Operating mechanism

In the event loop, once for each cycle of operation is called tick, tick of every task processing model is more complex, but the key steps are as follows:

  • Execute a macro task (not just get from the event queue stack)
  • If you encounter during the execution of micro tasks, it will be added to the micro-task job queue
  • After the task is finished macro, micro immediate implementation of all tasks of the current micro-tasks in the queue (order execution)
  • Current macro task is finished, start checking render, then render the GUI thread to take over
  • After the rendering is complete, JS thread continues to take over, at the beginning of a macro task (taken from the event queue)

Flowchart is as follows:

mark

Promise of async and immediate execution

We know that in the Asynchronous Promise embody thenand catchin so write in Promise code is executed as a synchronized task immediately. In async / await, before the emergence await appears with the code is executed immediately. Then there was what await when it happened?

await what has been done

从字面意思上看await就是等待,await 等待的是一个表达式,这个表达式的返回值可以是一个promise对象也可以是其他值。

很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。

这里感谢@chenjigeng的纠正:

由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。所以对于本题中的

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

等价于

async function async1() {
    console.log('async1 start');
    Promise.resolve(async2()).then(() ={
                console.log('async1 end');
        })
}

回到本题

//请写出输出内容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}

console.log('script start');  // 1

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

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

这道题主要考察的是事件循环中函数执行顺序的问题,其中包括asyncawaitsetTimeoutPromise函数。下面来说一下本题中涉及到的知识点。

以上就本道题涉及到的所有相关知识点了,下面我们再回到这道题来一步一步看看怎么回事儿。

  1. 首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。所以,上面例子的第一步执行如下图所示:

  2. 然后我们看到首先定义了两个async函数,接着往下看,然后遇到了 console 语句,直接输出 script start。输出之后,script 任务继续往下执行,遇到 setTimeout,其作为一个宏任务源,则会先将其任务分发到对应的队列中:
  3. script 任务继续往下执行,执行了async1()函数,前面讲过async函数中在await之前的代码是立即执行的,所以会立即输出async1 start
    遇到了await时,会将await后面的表达式执行一遍,所以就紧接着输出async2,然后将await后面的代码也就是console.log('async1 end')加入到microtask中的Promise队列中,接着跳出async1函数来执行后面的代码。
  4. script任务继续往下执行,遇到Promise实例。由于Promise中的函数是立即执行的,而后续的 .then 则会被分发到 microtask 的 Promise 队列中去。所以会先输出 promise1,然后执行 resolve,将 promise2 分配到对应队列。
  5. script任务继续往下执行,最后只有一句输出了 script end,至此,全局任务就执行完毕了。
    根据上述,每次执行完一个宏任务之后,会去检查是否存在 Microtasks;如果有,则执行 Microtasks 直至清空 Microtask Queue。
    因而在script任务执行完毕之后,开始查找清空微任务队列。此时,微任务中, Promise 队列有的两个任务async1 endpromise2,因此按先后顺序输出 async1 end,promise2。当所有的 Microtasks 执行完毕之后,表示第一轮的循环就结束了。
  6. 第二轮循环开始,这个时候就会跳回async1函数中执行后面的代码,然后遇到了同步任务 console 语句,直接输出 async1 end。这样第二轮的循环就结束了。(也可以理解为被加入到script任务队列中,所以会先与setTimeout队列执行)
  7. The second round is still the beginning of the cycle from the macro task queue. At this point the macro task only one setTimeout, direct output can be taken out, bringing the entire process ends.

Now I will change the code to impress.

A variant of formula

In a first variant of formula I are also a function async2 become Promise function code is as follows:

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

Can first see for yourself what the output sequence would be, let's publication of the results:

script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout

After the first macrotask been executed, that is output script endafter going to clean up all microtask. It will have output promise2, async1 end, promise4and the rest say no more.

Variant two

In a second variant, I will await later in async1 and code async2 are changed asynchronously, as follows:

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout2')
    },0)
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

Can first see for yourself what the output sequence would be, let's publication of the results:

script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1

Is output promise2after the next in the order will be sequentially added setTimeout output queue, we can see by the code sequence is added 3 2 1, it will be output in the order 3,2,1.

Variant three

Variant Third, I see a face neutralized the original title, on the whole very much the same, as follows:

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

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

Promise.resolve().then(() ={
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) ={
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) ={
    console.log(res)
    Promise.resolve().then(() ={
        console.log('promise3')
    })
})
console.log('script end')

Nothing more than to do the task in a micro piece of child-point article, if you are in front of the contents read the words This question is certainly no problem, the results are as follows:

script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout

Reference article

Guess you like

Origin www.cnblogs.com/nayek/p/11729924.html