事件循环

129 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情

事件循环

什么是事件循环?

因为 JS 是单线程的,如果有耗时较长的异步任务,不可能一直等它执行完成才继续执行后面的任务,所以设计出一种事件循环的机制来解决这个问题。

JS 代码执行时分为 同步任务异步任务,同步任务就是按照顺序执行,异步任务就需要先加入到一个事件队列中,等待当前所有同步任务执行完成后,再从事件队列中取出任务依次执行。

其中异步任务又分为 宏任务微任务 ,分别存储在 宏任务队列微任务队列

在每个宏任务执行完成后,都要去微任务队列依次取出微任务进行执行,直到清空微任务队列,然后开始执行宏任务队列中的下一个宏任务,如此循环,这个过程就称之为 事件循环

如何区分宏任务与微任务?

  • 微任务是 ES6 语法规定的(被压入 micro task queue)
  • 宏任务是由浏览器规定的(通过 Web APIs 压入 Callback queue)

宏任务

image-20220415160813164.png

微任务

image-20220415160846341.png

举例说明

// 可以将整个代码片段当做是一个宏任务 mac1
console.log('1')

// setTimeout 是一个宏任务,mac2
setTimeout(function () {
  console.log('2') // 2-1
  process.nextTick(function () {
    console.log('3') // mic3
  })
  new Promise(function (resolve) {
    console.log('4') // 2-2
    resolve()
  }).then(function () {
    console.log('5') // mic4
  })
})

// process.nextTick 是一个微任务 mic1
process.nextTick(function () {
  console.log('6')
})

// new Promise 中的回调函数是同步执行的,then 方法才是异步执行的
new Promise(function (resolve) {
  console.log('7') // 属于 mac1 中的同步代码
  resolve()
}).then(function () {
  // 是一个微任务,mic2
  console.log('8')
})

// mac3
setTimeout(function () {
  console.log('9') // 3-1
  process.nextTick(function () {
    console.log('10') // mic5
  })
  new Promise(function (resolve) {
    console.log('11') // 3-2
    resolve()
  }).then(function () {
    console.log('12') // mic6
  })
})

执行流程:

  1. 执行宏任务 mac1 中的同步代码,输入 1、7 ,将 mic1 和 mic2 加入微任务队列
  2. 依次执行微任务队列中的代码,输出 6、8
  3. 执行宏任务 mac2 中的同步代码,输出 2、4,将 mic2 和 mic4 加入微任务队列
  4. 依次执行微任务队列中的代码,输出 3、5
  5. 执行宏任务 mac3 中的同步代码,输出 9、11,将 mic5 和 mic6 加入微任务队列
  6. 依次执行微任务队列中的代码,输出 10、12

因此最终输出为 1 7 6 8 2 4 3 5 9 11 10 12

async/await

ECMAScript2017 中添加了async functions await

async 关键字将一个同步函数变成一个异步函数,并将返回值变为 promise

await 可以放在任何异步的、基于 promise 的函数之前

在执行过程中,它会暂停代码在该行上,直到 promise 完成,然后返回结果

而在暂停的同时,其他正在等待执行的代码就有机会执行了

举例:

async function async1() {
  console.log('a')
  const res = await async2()
  // 以下代码相当于在 async2 的 then 方法中执行,记为 mic1
  console.log('res1', res)
  console.log('b')
  return 1
}

async function async2() {
  console.log('c')
  return 2
}

// 整个代码片段当做一个宏任务 mac1
console.log('d')

// mac2
setTimeout(() => {
  console.log('e')
}, 0)

async1().then((res) => {
  // mic3
  console.log('res2', res)
  console.log('f')
})

new Promise((resolve) => {
  console.log('g')
  resolve()
}).then(() => {
  // mic2
  console.log('h')
})

console.log('i')

执行流程:

  1. 执行宏任务 mac1 中的同步代码,输出 d
  2. 遇到 mac2 ,将其放入宏任务队列
  3. 执行 async1() ,输出 a
  4. 执行 async2(),输出 c ,并返回 2
  5. 此时 await 会阻塞 async2() 的返回赋值,将其后面的代码记为 mic1,加入微任务队列,然后跳出 async1 执行后面的代码
  6. 执行 new Promise,输出 g,将 mic2 加入微任务队列
  7. 输出 i ,此时宏任务 mac1 执行完毕,开始执行依次执行微任务队列
  8. 执行 mic1,输出 b,此时 async1 执行完毕,再将其 then 方法中的任务 mic3 加入微任务队列;执行 mic2,输出 h
  9. 然后执行 mic3,输出 f
  10. 最后执行宏任务 mac2,输出 e