微任务 promise、process.nextTick 、async 函数await下面的代码;
宏任务 setTimeout setInterval I/O script
同一次事件循环中 微任务永远在宏任务之前执行
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
遇到 new Promise直接执行,打印"马上执行for循环啦"
遇到then方法,是微任务,将其放到微任务的【队列里】
打印 "代码执行结束"
本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"
到此,本轮的event loop 全部完成。
下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"定时器开始啦"
setTimeout(_ => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
setTimeout
就是作为宏任务来存在的,而Promise.then
则是具有代表性的微任务,上述代码的执行顺序就是按照序号来输出的。
所有会进入的异步都是指的事件回调中的那部分代码
也就是说new Promise
在实例化的过程中所执行的代码都是同步进行的,而then
中注册的回调才是异步执行的。
在同步代码执行完成后才回去检查是否有异步任务完成,并执行对应的回调,而微任务又会在宏任务之前执行。
所以就得到了上述的输出结论1、2、3、4
。
多提一嘴async/await函数
因为,async/await
本质上还是基于Promise
的一些封装,而Promise
是属于微任务的一种。所以在使用await
关键字与Promise.then
效果类似:
setTimeout(_ => console.log(4))
async function main() {
console.log(1)
await Promise.resolve()
console.log(3)
}
main()
console.log(2)
async函数在await之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise
时传入的代码,await之后的所有代码都是在Promise.then
中的回调
接下来上一张宏任务微任务的图帮助理解:
所以可以看做是这样的:浏览器线程先执行同步任务,途中遇到异步任务就将其加入到等待任务队列中去,然后继续向下执行,等同步任务全部执行完毕后,再去等待任务队列中去将所有可执行的微任务逐个执行,执行完后在拿取第一个先到达执行条件的宏任务来执行,执行完后再去等待任务队列中清理执行完所有已到达执行条件的微任务,然后再拿取下一个宏任务来执行,如果宏任务执行产生微任务或者微任务执行产生宏任务就一样加入到等待任务队列中,然后还是按照主线程每次到等待队列中先执行完所以的微任务再逐个执行宏任务的顺序来走
async和await
async和await 是es6新增的关键字,用于把异步变同步;
async在函数定义时使用,用async定义的函数默认返回一个Promise实例,可以直接.then(async还可以定义对象的方法),如果async定义的函数执行返回的不是一个promise对象,那么就会给返回值包装成一个promise对象(将返回值放进promise实例的resolve方法中当做参数)
await (要和async一起使用,一般是在async声明的函数中使用)
await 会等待,等它右侧的代码执行完;
用法:
1. 如果await右侧是同步的代码,就会让同步代码执行;如果执行的是一个函数,还会把函数的返回值给到await左边的变量
let p;
async function f3() {
p = await 18;
console.log(p);
}
f3();
console.log(1);
console.log(p);
// 结果是1,undefined,18
// js执行的时候是从右向左执行的,先执行18然后遇到await会将await左边的连同下面的都放进微任务中去,所以外面的p打印会是undefined,然后同步代码执行完再执行微任务的时候将其执行,给p赋值,打印p
2. 如果await右侧是一个Promise实例,或者一个方法返回了Promise实例,await会等着Promise的实例resolve,并且在实例resolve之前,await后面的代码不执行;并且还会拿到Promise在resolve时传入的值,并且赋值给等号左侧变量;
async function f(){
return 10;
}
async function f3() {
let p = await f();
console.log(p);
}
f3();
console.log(1);
// 打印1,10,上面说了async定义的函数后面如果返回的不是一个promise对象而是一个普通值就会默认包装成一个promise对象,并将其返回值赋值给await左边的变量,如果我们不使用async而是写一个promise对象,如下:
function f(){
return new Promise(((resolve, reject) => {
resolve(10);
}))
}
async function f3() {
let p = await f();
console.log(p);
}
f3();
console.log(1);
// 会发现打印的依旧是1,10
3. await会把await下面的代码变成微任务;上面代码就能看出来,先打印1再打印10,就是因为await将下面的代码当做一个微任务整体放进了等待任务队列中