【转】javascript单线程和多线程-同步和异步-微任务和宏任务(二)

注:内容转载于JavaScript中的microtasks和macrotasks

之前在文章javascript单线程和多线程-同步和异步-微任务和宏任务(一)中记录了js单线程多线程,同步和异步的概念。但是在异步任务中,又有一些区别(微任务和宏任务)

如果一段JavaScript代码中包含了setTimeout几乎所有的前端同学都知道其代码会被延迟(异步)执行,但是如果代码中同时出现了setTimeout、await以及Promise resolve的话大家还能说出来他们的先后执行顺序么?先抛出一个网上流传的前端面试题,主要涉及的知识点是异步async/await,setTimeout,Promise resolve的执行先后顺序,也就是我们这里要讨论的microtaks(异步微任务)和macrotasks(异步宏任务):

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function() {
  console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise end");
});
console.log("script end");

如果想要完全解答出这个题目,需要了解JavaScript的同步队列和异步队列,以及异步队列中细分出的microtasks(微任务)和macrotasks(宏任务)

同步&异步

我们知道在JavaScript的运行过程中会优先执行同步的任务,如果遇到异步代码就会将异步代码放置到异步队列中,等同步代码执行完成之后再去执行异步队列中的代码。比如下面的代码:

console.log(1)
setTimeout(()=>{
    console.log(2)
}, 0)
console.log(3)
setTimeout(()=>{
    console.log(4)
}, 0)
console.log(5)

代码首先会执行第一行的console.log(1)。继续执行遇到了第一个异步代码setTimeout,此时JavaScript会把该片段代码放置到异步队列中(确切的说是macrotasks,后面会讨论到),然后继续往下执行同步代码console.log(3),然后又遇到了第二个setTimeout,同样将其放置到异步队列中继续执行同步代码console.log(5)。当所有的同步代码执行完成之后,再回过来检查异步任务中是否有还未执行的任务,如果有就按照顺序执行,就是我们这里的分别打印2和4的setTimeout两段代码了。

所以上面的代码的执行结果是:1 3 5 2 4

异步中的优先级:microtasks & macrotasks

那么在异步任务中有没有优先级呢?我们先看如下的代码:

console.log(1)
setTimeout(() => {
  console.log(2)
}, 0);
new Promise(function(resolve) {
  console.log("3");
  resolve();
}).then(function() {
  console.log("4");
});
console.log(5)

聪明的你一定知道,代码中的console.log(1),console.log(5) 是同步代码,会优于异步代码执行,但是对于setTimeout中的console.log(2)、Promise中的console.log("3")和Promise resolve中的console.log("4"),到底是谁先执行呢?

这里我们要引入microtasks(微任务)和macrotasks(宏任务)的概念,microtasks和macrotasks都是异步的任务,但区别是:

1.microtasks任务优先于macrotasks,也就是说当同步任务执行完成之后会优先执行microtasks中的任务,之后才轮到macrotasks中的任务

2.microtasks 的执行会优先于UI任务(实际上UI任务属于macrotasks),意味着如果你不停的向microtasks中插入任务就回导致页面停止响应

3.常见的microtasks有:process.nextTick, Promises(其中Promises的构造函数是同步的), queueMicrotask, MutationObserver

4.常见的macrotasks有:setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering

所以对于上面的代码来说:

1.先执行同步代码console.log(1)

2.遇到setTimeout 放入 macrotasks 中

3.遇到Promise 执行同步的构造函数console.log("3"); resolve()(Promise的构造函数是同步执行的!Promise的构造函数是同步执行的!Promise的构造函数是同步执行的!) 然后把 then 放入到 microstaks中

4.继续执行同步代码console.log(5)

5.同步代码执行完成,执行microstaks中的任务,也就是Promise.then中的console.log("4")

6.microstaks执行完毕,执行macrostaks中的任务,即setTimeout的console.log(2)

所以结果是:1 3 5 4 2

异步中的async和await片段执行顺序

我们继续看例子:

async function async1(){
    console.log(2)
    await async2()
    console.log(3)
}
async function async2(){
    console.log(4)
}

console.log(1)
async1()
console.log(5)
Promise.resolve().then(() => console.log(6))
console.log(7)

Hmmm,让我们猜猜执行顺序:

1.首先是同步代码console.log(1)

2.然后是 async1 中的同步代码 console.log(2)

3.接着是 async2 中的同步代码 console.log(4)

然后呢??? 这个await怎么处理?await后面的代码又如何处理呢?

其实 async 函数返回一个的是一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先执行 await 后面的方法,然后将之后的代码全部放入到microtasks中并继续执行同步任务,所以这里就会在执行完async1 中的 await async2()之后把后面的console.log(3)放入到microtasks。

继续就是:

4.将console.log(3) 放入到microtasks中,跳出async1继续执行同步代码console.log(5)

5.遇到Promise,同步执行Promise的构造函数,这里Promise构造函数为空。

6.将Promise的then中的代码放到microtasks中

7.继续执行同步代码console.log(7)

8.同步代码执行完成,开始执行microtasks中的代码,先后顺序是async1中的console.log(3)、Promise.then中的console.log(6)

所以结果是:1 2 4 5 7 3 6

好了,相关的概念都解释完了,再让我们回过头来看看那个著名的面试题目吧:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function() {
  console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise end");
});
console.log("script end");

1.首先执行同步代码console.log("script start")

2.遇到setTimeout放到 macrotasks 宏任务中

3.继续执行同步代码async1中的console.log("async1 start")

4.同步执行 async2 中的console.log("async2");,并将await之后的代码统一放入到 microtasks 中

5.遇到Promise,同步执行其构造函数 console.log("promise1"); 并将其resmove之后的then函数放入到 microtasks 中

6.继续同步执行最后一行的console.log("script end"),同步任务执行完毕, 检查microtasks中是否有任务,并执行

7.执行microtasks中的第一个任务async1 await之后的内容console.log("async1 end");

8.执行microtasks下一个任务,Promise resolve中的console.log("promise end");

9.microtasks执行完毕,检查macrotasks中是否有任务

10.执行macrotasks中的setTimeout回调 console.log("setTimeout")

所以结果是:

script start
async1 start
async2
promise1
script end
async1 end
promise end
undefined
setTimeout

 欢迎转载:转载时请注明本文出处及文章链接

标签:


添加新评论

captcha
请输入验证码