08月08, 2022

JS进阶(10)--Promise(5)--事件循环再认识

事件循环再认识

宏任务与微任务

前面我们已经简单给大家介绍了浏览器器中的事件循环,如图:

事件循环中的异步队列有两种:宏任务( macro )队列和微任务( micro )队列。

宏任务队列有一个,微任务队列只有一个

  • 常见的宏任务有:setTimeout、setInterval、requestAnimationFrame、script等。
  • 常见的微任务有:new Promise( ).then(回调)、MutationObserver 等。

一个完整的事件循环过程,可以概括为以下阶段:

  • 查看执行栈中是否有同步代码,先执行同步代码
  • 如果执行栈中的同步代码清空,就会从浏览器宿主中调入对应的异步代码
  • 宏任务加入到宏任务队列,微任务加入到微任务队列
  • 如果事件队列中,同时存在宏任务与微任务,那么先将事件队列中的所有微任务加入到执行栈中执行,直到将执行栈清空
  • 然后再将事件队列中的所有宏任务加入到执行栈中执行,直到将执行栈清空
  • 如此循环,直到所有代码执行完成

宏任务与微任务谁先执行?

但是,如果你仔细研究这个图,其实你会发现一个问题,首先进入执行栈的,其实是宏任务 如下:

但是,这里第一出现的宏任务,其实是浏览器解析html和script脚本的任务,也就是第一次加载执行同步代码的时候,就是所谓最早的宏任务

我这里的说法,为了大家不在这里绕晕,你可以完全忽略所谓的最早的宏任务,就把这个直接说成执行栈中加入同步代码,然后再区分微任务和宏任务,所以你可以这里简单理解,当第一次加执行栈中加载完之后,微任务就是事件队列的VIP,微任务队列中有微任务,那么首先执行微任务,再去执行宏队列中的任务

因此,下面的分析中,我都直接忽略了第一次执行宏任务script的过程,直接做为加载执行栈进行讲解

当然这么说比较的抽象,我们通过一些面试题,就能理清楚这些脉络

面试题大多数都是围绕着Promise计时器展开的,所有,大家必须要清楚,什么时候是同步代码,什么时候是异步代码,什么是微任务,什么是宏任务,什么时候将代码加入到执行栈中

面试题分析

例1、下面的代码执行的结果是什么?

console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

答案:

script start
script end
promise1
promise2
setTimeout

分析:

首先会执行同步的任务,输出 script start 以及 script end。接下来是处理异步任务,异步任务分为宏任务队列和微任务队列,在执行宏任务队列中的每个宏任务之前先把微任务清空一遍,由于 promise 是微任务,所以会先被执行,而 setTimeout 由于是一个宏任务,会在微任务队列被清空后再执行。

例2、下面的代码执行的结果是什么?

Promise.resolve().then(()=>{
  console.log('Promise1')
  setTimeout(()=>{
    console.log('setTimeout2')
  },0)
})
setTimeout(()=>{
  console.log('setTimeout1')
  Promise.resolve().then(()=>{
    console.log('Promise2')
  })
},0)

答案:

Promise1
setTimeout1
Promise2
setTimeout2

分析:

先查看执行栈,然后将宏任务setTimeout与微任务Promise.resolve().then分别推入队列,先执行微任务,打印Promise1,然后发现宏任务setTimeout,再次把第二个宏任务推入到队列。

然后执行宏队列中的第一个setTimeout,打印setTimeout1,发现第二个微任务Promise.resolve().then,推入微任务队列。

这时微任务与宏任务队列中都有内容,因此,还是先执行微任务,打印Promise2,然后再执行宏任务,打印setTimeout2

例3:下面的代码执行的结果是什么?

const promise = new Promise((resolve, reject) => {
    console.log(1); 
    resolve(); 
    console.log(2);
})

promise.then(() => {
    console.log(3);
})

console.log(4);

答案:

1
2
4
3

分析:

注意Promise函数中的代码是同步代码,只有执行resolve()之后,才是异步代码

因此先执行console.log(1); console.log(2); ,然后是console.log(4);最后才是then中的异步代码console.log(3);

面试题

1:下面的代码执行的结果是什么?

const promise = new Promise((resolve, reject) => {
    console.log(1); 
    setTimeout(()=>{
      console.log(2)
      resolve(); 
        console.log(3);
    })
})

promise.then(() => {
    console.log(4);
})

console.log(5);

答案:

1
5
2
3
4

2:下面的代码执行的结果是什么?

async function m(){
  const n = await 1;
  console.log(n);
}

m();
console.log(2);

答案:

2
1

3:下面的代码执行的结果是什么?

async function m(){
  const n = await 1;
  console.log(n);
}

(async ()=>{
  await m();
  console.log(2);
})();

console.log(3);

答案:

3
1
2

4:下面的代码执行的结果是什么?

async function m1(){
  return 1;
}

async function m2(){
  const n = await m1();
  console.log(n)
  return 2;
}

async function m3(){
  const n = m2();
  console.log(n);
  return 3;
}

m3().then(n=>{
  console.log(n);
});

m3();

console.log(4);

答案:

Promise { <pending> [[PromiseResult]]: 2}
Promise { <pending> [[PromiseResult]]: 2}
4
1
3
1

5:下面的代码执行的结果是什么?

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

答案:

1

6:下面的代码执行的结果是什么?

var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(()=>{
    resolve();
  }, 1000);
}).then(() => {
  console.log('promise2');
}).then(() => {
  console.log('promise3');
}).then(() => {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  resolve(true);
  console.log('after2');
});

console.log('end');

答案:

promise1
undefined
end
promise2
promise3
promise4
Promise { <pending> }
after1
after2

7:下面的代码执行的结果是什么?

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('promise2');
});
console.log('script end');

答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

本文链接:http://www.yanhongzhi.com/post/js_ap_26.html

-- EOF --

Comments