1.任务队列(首先要明白,宏任务和微任务都是指的异步任务,普通同步代码(如 console.log
)会在当前执行栈中执行,而微任务和宏任务则涉及到异步操作和事件循环。)
简单来说,任务队列有两个,宏任务队列和微任务队列。代码执行时,宏任务进入到宏任务队列,微任务进入到微任务队列。那么哪些任务时微任务,哪些任务是宏任务呢?其实大部分的任务都属于宏任务。而微任务通常在代码运行时产生,通常是由Promise所创建的,Promise的then、catch、finally中的回调函数会作为微任务进入到微任务队列中。
例子:
setTimeout(() => {
console.log(1);
})
Promise.resolve().then(() => {
console.log(2);
})
解析:
-
首先,
setTimeout
会被放入宏任务队列(macrotask queue)中,等待主线程执行完毕后执行。由于setTimeout
是宏任务,它的执行会在微任务之后。 -
接着,
Promise.resolve().then()
会创建一个微任务(microtask),因为then
中的回调函数是微任务的一部分。微任务会在当前宏任务执行完毕之后立即执行。
现在,事件循环开始执行:
-
首先,执行主线程的任务,但在这段代码中主线程并没有其他任务,因此会继续往下执行。
-
然后,微任务队列中有一个任务,即
Promise.resolve().then(() => { console.log(2); })
,所以这个微任务会被执行。输出2
。 -
接下来,宏任务队列中有一个任务,即
setTimeout(() => { console.log(1); })
,所以这个宏任务会被执行。输出1
。
总结:这段代码的输出是 2
和 1
,因为微任务会在宏任务之前执行。
2.分类
宏任务(Macrotasks):
- setTimeout 和 setInterval:用于创建宏任务,指定的回调函数会被放入宏任务队列中。
- I/O 操作:诸如读写文件、操作数据库等异步任务。
- DOM事件:例如点击事件、输入事件等。
- AJAX请求的回调函数:AJAX完成时,其回调函数会被放入宏任务队列中。
微任务(Microtasks):
- Promise的then方法:Promise对象状态改变时,其回调会被放入微任务队列。
- MutationObserver:用于监听DOM变化,回调函数会被放入微任务队列。
- process.nextTick(在Node.js环境中):将回调函数放入当前执行栈的微任务队列中。
在事件循环中,每个宏任务执行完毕后,都会立即检查微任务队列,并执行其中的所有微任务。这确保微任务在宏任务之前执行,使得微任务具有更高的优先级。
3.难度升级
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1();
setTimeout(function() {
console.log('setTimeout');
}, 0)
new Promise(resolve => {
console.log('Promise');
resolve();
})
.then(function() {
console.log('promise1');
})
.then(function() {
console.log('promise2');
})
console.log('script end');
先不要看正确答案,自己想想结果会是什么?
-
console.log('script start');
:输出'script start'
。 -
async1()
被调用:- 进入
async1
函数。 await async2();
会执行async2()
,输出'async2 end'
。console.log('async1 end');
被放入微任务队列。
- 进入
-
setTimeout(function() { console.log('setTimeout'); }, 0);
:将setTimeout
回调放入宏任务队列。 -
new Promise(resolve => { console.log('Promise'); resolve(); })
:输出'Promise'
,并将两个.then
的回调放入微任务队列。 -
console.log('script end');
:输出'script end'
。 -
执行微任务队列:
- 执行
console.log('async1 end');
,输出'async1 end'
。 - 执行
.then(function() { console.log('promise1'); })
,输出'promise1'
。 - 执行
.then(function() { console.log('promise2'); })
,输出'promise2'
。
- 执行
-
执行宏任务队列:
- 执行
setTimeout
的回调,输出'setTimeout'
。
- 执行
最终结果:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
这里涉及到两个知识点:
1)promise是同步代码,.then才是异步任务
2)await的执行分两种情况:
-
如果await后面直接跟的为一个常量,比如:await 1,这种情况的话相当于直接把await后面的代码注册为一个微任务。可以简单理解promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise的时候,会注册promise.then()中的回调函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),在执行async1函数后面注册的微任务代码(promise1、promise2)。
-
如果await后面跟的是一个异步函数的调用,比如将上面的代码改成如下代码:( 该种情况下,执行完await并不先把await后面的代码注册到微任务队列中去,而是执行完await之后直接跳出async1函数,执行其他代码。然后遇到promise的时候,把promise.then中的回调函数注册为微任务。其他代码执行完毕后,需要回到async1函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中,注意此时微任务队列中是有之前注册的微任务的。所以这种情况会先执行async1函数之外的微任务(promise1、promise2),然后才执行async1内注册的微任务(async1 end);)
console.log('script start'); async function async1() { await async2(); console.log('async1 end'); } async function async2() { console.log('async2 end'); return Promise.resolve().then(() => { console.log('async2 end1'); }) } async1(); setTimeout(function() { console.log('setTimeout'); }, 0) new Promise(resolve => { console.log('Promise'); resolve(); }) .then(function() { console.log('promise1'); }) .then(function() { console.log('promise2'); }) console.log('script end');
输出结果是:script start => async2 end => Promise => script end =>async2 end1 => promise1 => promise2 => async1 end => setTimeout
解析过程:
-
console.log('script start');
:输出'script start'
。 -
async1()
被调用:- 进入
async1
函数。 await async2();
会执行async2()
。
- 进入
-
async2()
被调用:- 执行
console.log('async2 end');
,输出'async2 end'
。 - 返回一个 Promise,并在微任务队列中添加一个
.then(() => { console.log('async2 end1'); });
。【!!!!!!!!注意此时跳出async1()的执行】
- 执行
-
setTimeout(function() { console.log('setTimeout'); }, 0);
:将setTimeout
回调放入宏任务队列。 -
new Promise(resolve => { console.log('Promise'); resolve(); })
:输出'Promise'
,并将两个.then
的回调放入微任务队列。 -
console.log('script end');
:输出'script end'
。 -
此时回到async1(),将
console.log('async1 end');加入微任务队列
-
执行微任务队列:
- 执行
async2
中的.then(() => { console.log('async2 end1'); });
,输出'async2 end1'
。 - 执行
Promise
中两个.then
的回调,输出'promise1'
和'promise2'
。 - 执行console.log('async1 end');,输出‘async1 end’
- 执行
-
执行宏任务队列:
- 执行
setTimeout
的回调,输出'setTimeout'
。
- 执行