参考链接:
一、异步
(一)原理
js执行的优先级:同步 > 微队列 > 宏队列- js中用来存储待执行回调函数的队列包含2个不同特定的列队,
- 宏列队:用来保存待执行的宏任务(回调),比如:
定时器回调/DOM事件回调/ajax回调 - 微列队:用来保存待执行的微任务(回调),比如:
promise的回调/MutationObserver的回调
- 宏列队:用来保存待执行的宏任务(回调),比如:
- JS执行时会区别这2个队列
- JS 引擎首先必须先执行所有的初始化同步任务代码
- 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行
- 关于promise
①then是同步执行的,then里面的回调是异步的,所以后面的回调不一定马上放到队列
②then里面的回调是否执行,要取决于他前面promise的执行结果,如果是resolve或reject就执行,如果还是pending就不会执行回调
③若没有resolve或reject,但所有.then执行完毕,返回undefined,也表示当前微任务结束
(二)例题
思路:找出同步,再找微任务和宏任务,再看是否存在无效的数据
1. 例题(难度☆)
setTimeout(() => {
console.log(1);
}, 0);
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
});
console.log(5);
// 2 5 3 4 1
// 宏:[1]
// 微:[3 4]
2. 例题(难度☆☆☆)
const first = () => (new Promise((resolve, reject) => {
console.log(3);
const p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
}, 0);
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
}));
first().then((arg) => {
console.log(arg);
});
console.log(4);
// 解析:
// 同:[3 7 4]
// 宏:[5]
// 微:[1 2]
答案:
(1) QA:为什么resolve(6)是无效的?
- promise状态只能改一次,
resolve(6)之前已经resolve(1)过一次了,所以定时任务到了之后再执行resolve(6)是无效的
(2) QA:为什么先输出1再输出2?
- p new的时候是马上执行的
- p的状态执行
resolve(1)后立马转为成功,所以p.then()的回调先进入微任务队列; - p运行完之后才执行到
resolve(2),这个时候first().then()的回调才进入微任务队列, - 所以先输出1,再输出2
3. 例题(难度☆☆☆☆☆)
同步:1 7 2 3 8 4
宏任务:0
微任务: 4
setTimeout(() => {
log('0')
}, 0)
new Promise((resolve, reject) => {
log('1')
resolve()
})
.then(() => {
log('2')
new Promise((resolve, reject) => {
log('3')
resolve()
})
.then(() => {
log('4')
})
.then(() => {
log('5')
})
})
.then(() => {
log('6')
})
new Promise((resolve, reject) => {
log('7')
resolve()
}).then(() => {
log('8')
})
(1) QA:为什么3之后不输出4而输出8?
- 8比4先进入队列
(2) QA:为什么4之后不输出5输出6?/ 为什么4之后微任务2执行完毕?
- 因为log4之后会立马执行.then(then是同步执行的,但后面的回调log5不一定马上放到队列,因为log4还未执行完毕(pending状态),所以无法执行log5
- 无法执行log5,但意味着log2的微任务结束了(所有.then执行完毕,返回undefined),log6先进入队列),所以先输入6再输出5
(3) 输出顺序从上到下进行分析
- 先执行完初始同步任务
- 遇到定时器setTimeout,放入宏队列(log0)
- 遇到第1个 new Promise (1) 立即执行,直接执行输出 log1, ——1
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log2被放入微队列
(MicroTask2)- 因为后续任务log3、log4、log5、log6依赖于微任务log2,
- 所以暂时不会将该部分放入微队列,
- 需等执行到(log2)时才轮到
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log2被放入微队列
- 遇到第2个 new Promise(2) 立即执行,直接执行输出log 7 , ——7
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log8被放入微队列
(MicroTask8)
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log8被放入微队列
- 开始执行微任务
- 执行微任务 MicroTask2,输出 2, ——2
- 遇到第3个new Promise (3) 立即执行,输出3, ——3
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log4被放入微队列
(MicroTask4) - 跟着执行log4后的.then(),
- 因为此时 MicroTask4 刚进入微队列中,log5依赖于MicroTask4的执行结果,所以log5不会被放入微队列中(原理4-①②)
- 跟着执行resolve()立即完成,所以后面的.then()中的回调log4被放入微队列
- 往下走MicroTask2中没有resolve或reject,但所有.then执行完毕,微任务 MicroTask2 结束(原理4-③、疑点2)
- 所以MicroTask2的.then()中的回调log6被放入微队列中
(MicroTask6)
- 所以MicroTask2的.then()中的回调log6被放入微队列中
- 执行微任务 MicroTask8 ,输出8,——8
- 执行微任务 MicroTask4 ,输出4,——4
- 此时微任务 MicroTask4 执行完毕,返回undefined,所以之前log5中的回调被放入微任务队列中
(MicroTask5)
- 此时微任务 MicroTask4 执行完毕,返回undefined,所以之前log5中的回调被放入微任务队列中
- 执行微任务(log6),输出6,——6
- 执行微任务(log5),输出5,——5
- 微任务执行完毕,执行宏任务,输出0,——0
- 结果:1,7,2,3,8,4,6,5,0
(二)async 、await()
顺序:await > 微任务 > 宏任务
async关键字是将一个同步函数变成一个异步函数,并将返回值变为promise。- 而
await可以放在任何异步的、基于promise的函数之前。在执行过程中,它会暂停代码在该行上,直到promise完成,然后返回结果值。而在暂停的同时,他正在等待执行的代码就有机会执行了。
[问题延伸]
1. 宏任务和微任务都有哪些⭐⭐⭐⭐⭐
宏任务:script、setTimeOut、setInterval、setImmediate
微任务:promise.then、process.nextTick、Object.observe、MutationObserver
注意:Promise是同步任务
2. 宏任务和微任务都是怎样执行的⭐⭐⭐⭐⭐
- 执行宏任务script,
- 进入script后,所有的同步任务主线程执行
- 所有宏任务放入宏任务执行队列
- 所有微任务放入微任务执行队列
- 先清空微任务队列,
- 再取一个宏任务,执行,再清空微任务队列
- 依次循环