JS中setTimeout、promise、async、await的执行顺序


字数:1.6k 阅读时长:6分钟 阅读:85

我们都知道,Javascript单线程语言,也就是说,同一个时间只能做一件事。这就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
因为任务有同步异步之分,所以不同任务的执行必定有一定的顺序。

JavaScript

Event Loop(事件循环)

  • 所有任务可以分成两种:
  1. 同步任务(synchronous):在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
  2. 异步任务(asynchronous):不进入主线程、而进入任务队列(task queue)的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  • 异步执行的运行机制:(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

定义: 主线程循环不断的从任务队列中读取事件(任务),这整个过程就称为Event Loop事件循环)。

JavaScript事件

概述: 任务队列是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在任务队列中添加一个事件,表示相关的异步任务可以进入执行栈了。主线程读取任务队列,就是读取里面有哪些事件。

JavaScript的事件分两种,宏任务(macro-task)和微任务(micro-task)

  1. 宏任务:从上到下、从左到右、顺序执行的script代码,setTimeout,setInterval,setImmediate(node环境)
  2. 微任务:Promise.then(非new Promise),async/await整体,process.nextTick(node环境)
  3. 其他异步任务:
    • requestAnimationFrame(callback) - RAF 仅仅存在于浏览器环境,执行时机在 setTimeout 之前执行
    • queueMicrotask:执行时机在第一轮同步任务执行完成之后,微任务执行之前。

setTimeOut

setTimeOut执行需要满足两个条件:

  1. 主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回调函数
  2. 这个回调函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行

注意: setTimeOut并不是直接的把回调函数放进异步队列中,而是在定时器的时间到了之后,把回调函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。

promise、async/await

  • new Promise同步的任务,会被放到主进程中去立即执行。
  • .then()函数是异步任务会放到异步队列中去,具体就是当promise状态结束(fulfilled)的时候,会立即放进异步队列中去。
  • async关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数
  • await关键字要在 async 关键字函数的内部await 写在外面会报错。await如同他的语意,就是在等待,等待右侧的表达式完成。
  • await会让出线程,阻塞async内后续的代码,先去执行async外的代码。等外面的同步代码执行完毕,才会执行里面的后续代码。就算await的不是promise对象,是一个同步函数,也会同样执行。

执行顺序(总结):

  1. 宏任务(从上到下、从左到右的整体)
  2. 微任务的Event Queue(Promise.then,async / await整体,process.nextTick【node环境】)
  3. 宏任务的Event Queue(setTimeout / setInterval / setImmediate【node环境】)
  4. 同一轮微任务队列中,依次顺序执行 process.nextTickqueueMicrotaskPromise.thenasync/await
  5. 同一轮宏任务队列中,setImmediatesetTimeout 之后执行
  6. 浏览器环境同一轮任务队列中,requestAnimationFramesetTimeout 之前执行

经典面试题:

1、setTimeout + promise

  • 例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
;(function() {
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(data => {
console.log(3);
});
});
new Promise((resolve) => {
resolve();
console.log(4);
}).then(() => {
console.log(5);
setTimeout(() => {
console.log(6);
});
}).then(() => console.log(7));
console.log(8);
})();
  • 输出:
1
2
3
4
5
6
7
8
1
4
8
5
7
2
3
6
  • 例2:
1
2
3
4
5
6
7
8
9
10
11
function asyncFn() {
console.log(1);
Promise.resolve().then(data => {
console.log(2);
});
console.log(3);
}

setTimeout(() => {
asyncFn();
}, 20);
  • 输出:
1
2
3
1
3
2

2、setTimeout + promise + async + await

  • 例3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;(function() {
async function async1 () {
console.log("async1 start");
await async2();
console.log("async1 end");
}

async function async2 () {
console.log("async2");
}

console.log("script start");
setTimeout(() => {
console.log("setTimeOut");
}, 0);
async1();
new Promise(function(reslove) {
console.log("promise1");
reslove();
}).then(function() {
console.log("promise2");
});
console.log("script end");
})();
  • 输出:
1
2
3
4
5
6
7
8
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeOut

3、setTimeout + promise + async + await + setImmediate + process.nextTick

  • 例4(在node环境中):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
console.log('global');

setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')
})
})

setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')
})
})

process.nextTick(function() {
console.log('global_nextTick');
})

new Promise(function(resolve) {
console.log('global_promise');
resolve();
}).then(function() {
console.log('global_then')
})

setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')
})
})

process.nextTick(function() {
console.log('global2_nextTick');
})

new Promise(function(resolve) {
console.log('global2_promise');
resolve();
}).then(function() {
console.log('global2_then')
})

async function async1() {
console.log('async_1')
}

;!async function() {
await async1()
console.log('async_await')
}();

setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')
})
})
  • 输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
global
global_promise
global2_promise
async_1
global_nextTick
global2_nextTick
global_then
global2_then
async_await
timeout1
timeout1_promise
timeout1_nextTick
timeout1_then
timeout2
timeout2_promise
timeout2_nextTick
timeout2_then
immediate1
immediate1_promise
immediate1_nextTick
immediate1_then
immediate2
immediate2_promise
immediate2_nextTick
immediate2_then

总结

  1. script
  2. process.nextTick (node环境)
  3. queueMicrotask
  4. promise.then / async|await
  5. requestAnimationFrame (浏览器环境)
  6. setTimeout
  7. setImmediate (node环境)

参考文档:


欢迎访问:个人博客地址

本文作者: Tiven
发布时间: 2021-10-09
最后更新: 2022-02-21
本文标题: JS中setTimeout、promise、async、await的执行顺序
本文链接: https://www.tiven.cn/p/4d100461/
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!
欢迎留言,提问 ^_^