[JavaScript] setTimeout, Promise, Async / Await execution order (fine)


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

Recently we saw the front face questions about such an event loop:

//请写出输出内容
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');

Output:

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

This question is mainly on the execution order of the event loop function, including async, await, setTimeout, Promise function. Here's what this question involved in the knowledge points.

Task Queue concept

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 a job queue event
  • 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, started

According to the specification, the event loop is to be coordinated through the mechanism of the task queue. 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

Here Insert Picture Description

Event cycle (Event Loop)

The concept:
JS main thread constant cycle of reading from the task queue task, perform tasks, which runs the event loop mechanism called (event loop).

Note:
Each event loop has a microtask queue
for each event loop have one or more macrotaks queue (also can be called task queue)
a task task can be put into macrotask queue microtask queue in
each event loop It will be performed first microtask queue, after the execution is complete, extract a task macrotask queue to join microtask queue, then continue microtask queue, in order to perform all tasks continue until the end of execution.

Macro task (macrotasks)

The concept:
(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 a callback and event execution stack into execution from the event queue)

Process:
Browser To be able to make internal 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 re-rendering process as follows:

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

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

  • macrotasks
    setTimeout
    setImmerdiate
    setInterval
    I/O
    UI 渲染

Micro-task (microtasks)

The concept:
Microtask (also known as micro-task), it is understood that the tasks performed immediately after the end of the current task execution. That is, before the current task after task, the next task, before rendering.

Process:
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)

(macro) task -> (micro) task -> 渲染 -> (macro) task -> …

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

  • microtasks
    process.nextTick
    promise
    Object.observe (废弃)
    MutationObserver

Operating mechanism

In the event loop, once for each cycle of operation is called tick, tick each 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:
Here Insert Picture Description

Promise of async and immediate execution

We know that in the Asynchronous Promise embody the catch and then, 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

From the point of view await the literal meaning is to wait, await waiting is an expression, the expression of the return value can be a promise object can also be other values.

Many people think that after the await expression will wait after the execution will continue behind the code is actually await a sign to let a thread. await the latter expression will first perform again, will await behind the code into microtask, and then will jump out of the whole async function to execute the code behind

review:

Because as async await itself promise + of syntactic sugar generator . Therefore await code behind microtask. Such modification can be present for the first title in question out:

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

Equivalent to

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

Analysis of this question

Above all the relevant knowledge on questions related to this, let's go back to this question step by step look at how children.

  1. First of all, the event loop from the macro task (macrotask) queue began, this time, the macro task queue, only one script (whole Code) mission; when faced with the task source (task source), will hand out the task to the corresponding task queue. Therefore, the first step in the above example is performed as shown below:
    Here Insert Picture Description

  2. Then we saw first define the two async function, then look down, then met console statements, direct output script start. After output, script task to continue down, encountered setTimeout, as a macro task source, it will distribute its first mission to the corresponding queue:
    Here Insert Picture Description

  3. script task to continue down the implementation of the async1 () function, said earlier async function code is immediately prior to await execution, it will be immediately output async1 start.

    When await encountered, it will await the expression that performed again, so we immediately output async2, then await later code is console.log('async1 end')added to the microtask Promise queue, then back out async1 function code to execute:
    Here Insert Picture Description

  4. Continue down the script task execution, met Promise instance. Promise Since the function is executed immediately, and the subsequent .then will be distributed to the microtask Promise queue. You will first output promise1, then execute resolve, assigned to the corresponding queue promise2:
    Here Insert Picture Description

  5. script task to continue down, and finally only one output of the script end, so far, the overall task is finished.

    According to the above, after each finished execute a macro task will be to check whether there is Microtasks; if so, until the execution Microtasks empty Microtask Queue.

    Thus the task after the script is finished, start looking for emptying micro task queue. At this time, the micro-task, Promise two queue some tasks async1 end and promise2, thus according to the order output async1 end, promise2. When all Microtasks finished, it represents the first round of the cycle is over.

  6. The second round is still the beginning of the cycle from the macro task queue. At this point the macro task in only a 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');

You 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, after the output script end, will go to clean up all microtask. It will have an output promise2, async1 end, promise4, the rest say no more

Variant di (deformation)

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() {
		alert('setTimeout2')
	},0)
}
console.log('script start');

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

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

Output structure:

script start
async1 start
promise1
script end
promise2
(执行setTimeout2对应的逻辑,此时为阻断式,只有用户交互确认后再执行下面的定时器)
setTimeout1
setTimeout3

After the output is promise2, the next will be sequentially output in the order setTimeout join the queue, by the beginning of the code we put in order the queue is setTimeout3 -> setTimeout2 -> setTimeout1, when the execution is carried out in sequence, but is not waiting on a timer implemented within next execution timer, this time to see who is who should perform outputs, this time can be known by the code setTimeout2 -> setTimeout1are executed immediately, in order to perform setTimeout2, but the logic here is denial-of Alert, so only the end of the interaction, will continue with the rest of the timers, the last time sequentially output according to the timingsetTimeout1 -> setTimeout3

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, there may also be modified here:

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

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

Equivalent to

new Promise((resolve) => {  
    console.log('promise2')
    resolve();
}).then(() => {
	Promise.resolve().then(() => {
        console.log('promise2.then')
    })
}).then(() => {
	Promise.resolve().then(() => {
        console.log('promise3')
    })
})

The results are as follows:

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

Reference article

Published 134 original articles · won praise 80 · views 30000 +

Guess you like

Origin blog.csdn.net/Umbrella_Um/article/details/100698686