宏任务:setTimeout ,setinterval,click等引起的异步操作
微任务:promise.catch,nexttick,promise.then 的异步操作
在工作的时候,遇到了这样一段代码
const link = document.createElement('a');
link.href = file_cur.value;
link.download = '原文档';
link.click();
// 使用 setTimeout 来延迟执行打印操作
setTimeout(() => {
document.body.removeChild(link);
}, 0);
刚开始不是很了解这里的setTimeout的含义,经过相关资料的查询发现,这里是借用了宏任务周期的概念,具体是有一个或多个宏任务队列,队列内是同步代码,在执行栈执行完所有代码后,会先将微任务队列执行完,之后在将一个宏任务压入执行栈。这里贴出一个例子
const b = new Promise((resolve) => {
setTimeout(() => {
console.log('resolve');
console.log('a',a);
resolve('resolved');
}, 0);
console.log('同步操作开始');
}).then((resolve) => {
console.log('宏任务结束');
console.log(resolve)
return 'b'
});
console.log('同步操作完成');
console.log("b", b)
const a = new Promise(function (resolve, reject) {
console.log(1111);
return resolve(2222);
}).then(function (value) {
console.log(value);
console.log('a_in_then',a);
return 3333;
})
const promiseA = new Promise((resolve, reject) => {
resolve(777);
});
// 此时,“promiseA”已经敲定了
promiseA.then((val) => console.log("异步日志记录有值:", val));
console.log("立即记录");
console.log(a)
打印顺序是1.同步操作开始
2.同步操作完成
3. b pending(注:这里是一个引用,所以展开引用后里面之所以是fulfilled是因为后面b已经完成了“承诺”也就是then函数,另外并不是执行完resolve就改变状态的)
4. 1111
5. 立即记录
6. promise pending
7. 2222
8.a_in_then promise pending
9. 777
10. resolve
11. a fulfilled 3333
12. 宏任务结束
13. resolved
理解上面的执行顺序和打印内容需要明确以下几个关键点
1.宏任务内的同步代码执行完成之后,才会开始promise的回调函数,也就是微任务
2.promise的构造函数内传入的函数算是同步代码,立即执行
3.无论是在宏任务还是在微任务中遇到的settimeout,都算是一个新的宏任务,不在当前宏任务内执行
4.宏任务之间执行的顺序就是加入队列的顺序
5.promise构造函数和then其实都是返回一个promise对象,这个对象的状态在其真正实现或者拒绝之后才会改变
这里开始实际分析为什么代码这么执行以及结果
const b = new Promise((resolve) => {
setTimeout(() => {
//10.这是一个新的宏任务周期
console.log('resolve');
//11.到了这个新的周期后,a早已经执行完毕,所以是fulfilled 3333,这里的3333是then的返回值
console.log('a',a);
resolve('resolved');
}, 0);
//1.因为promise构造函数内的函数是等同于同步代码块的,所以打印同步操作开始,上面的setTimeout相当于注册了一个宏任务,里面的具体代码是下一个周期执行
console.log('同步操作开始');
}).then((resolve) => {
//12. 到了这里新的宏任务已经结束了,开始执行微任务队列
console.log('宏任务结束');
//13. 打印的是构造函数返回值
console.log(resolve)
return 'b'
});
//2. 打印同步操作没什么可说的
console.log('同步操作完成');
//3. b对象还没有真正实现,也就是没有执行完then 或者catch ,所以是pending
console.log("b", b)
const a = new Promise(function (resolve, reject) {
//4.原因和1是一样的
console.log(1111);
return resolve(2222);
}).then(function (value) {
//7. 这个是在本次执行栈空后,要执行微任务队列,也就是promise的回调函数,那为什么不是b的回调函数呢,那是因为b因为resolve放在了setTimeout内部,只有在轮到它的宏任务周期后,才会将回调函数放入微任务队列。
console.log(value);
//8. then没有执行完,所以是pending
console.log('a_in_then',a);
return 3333;
})
const promiseA = new Promise((resolve, reject) => {
resolve(777);
});
// 此时,“promiseA”已经敲定了
//9. 打印这个也没啥好说的,因为是任务队列的第二个回调函数,至此,开始了下一个宏任务周期
promiseA.then((val) => console.log("异步日志记录有值:", val));
//5. 没啥好说的
console.log("立即记录");
//6.没啥好说的
console.log(a)
打印顺序是1.同步操作开始
2.同步操作完成
3. b pending(注:这里是一个引用,所以展开引用后里面之所以是fulfilled是因为后面b已经完成了“承诺”也就是then函数,另外并不是执行完resolve就改变状态的)
4. 1111
5. 立即记录
6. promise pending
7. 2222
8.a_in_then promise pending
9. 777
10. resolve
11. a fulfilled 3333
12. 宏任务结束
13. resolved
参考文献:
面试率 90% 的JS事件循环Event Loop,看这篇就够了!! !_面试率超高的js事件循环,看这篇就够了-优快云博客Promise - JavaScript | MDN (mozilla.org)
javascript - 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 - 程序生涯 - SegmentFault 思否