一道面试题带你吃透 Javascript宏任务和微任务执行顺序

603 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

由一道面试题引出本文:

“async function async1() {console.log(‘async1 start‘);await async2();console.log(‘async1 end‘)”

写出下边程序输出内容:

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

步骤分析:执行顺序参考这里

  • 首先,事件循环从宏任务(macrostack)队列开始,这个时候,宏任务(整体script、setTimeout、setInterval)队列中,只有一个 script (整体代码)任务 ()。
  • 首先执行 console.log('script start'),输出 ‘script start'
  • 遇到 setTimeout 把 console.log('setTimeout') 放到 macrotask 队列中
  • 执行 aync1() 输出 ‘async1 start' 和 'async2' ,把 console.log('async1 end') 放到 micro 队列中
  • 执行到 promise ,输出 'promise1' ,把 console.log('promise2') 放到  micro 队列中
  • 执行 console.log('script end'),输出 ‘script end'
  • macrotask 执行完成会执行 microtask ,把 microtask quene 里面的 microtask 全部拿出来一次性执行完,所以会输出 'async1 end' 和 ‘promise2'
  • 开始新一轮的事件循环,去除执行一个 macrotask 执行,所以会输出 ‘setTimeout'

涉及知识点:

  • Promise 优先于 setTimeout 宏任务,所以 setTimeout 回调会最后执行
  • Promise 一旦被定义就会立即执行
  • Promise 的 resolve 和 reject  是异步执行的回调。所以 resolve() 会被放到回调队列中,在主函数执行完和 setTimeout 之前调用
  • await 执行完后,会让出线程。async 标记的函数会返回一个 Promise 对象

JS层原理

js是单线程的,基于事件循环,非阻塞IO的。
特点: 处理I/O型的应用,不适合CPU运算密集型的应用。
说明: 事件循环中使用一个事件队列,在每个时间点上,系统只会处理一个事件,即使电脑有多个CPU核心,也无法同时并行的处理多个事件。因此,node.js在I/O型的应用中,给每一个输入输出定义一个回调函数,node.js会自动将其加入到事件轮询的处理队列里,当I/O操作完成后,这个回调函数会被触发,系统会继续处理其他的请求。