一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情。
事件循环
什么是事件循环?
因为 JS 是单线程的,如果有耗时较长的异步任务,不可能一直等它执行完成才继续执行后面的任务,所以设计出一种事件循环的机制来解决这个问题。
JS 代码执行时分为 同步任务 和 异步任务,同步任务就是按照顺序执行,异步任务就需要先加入到一个事件队列中,等待当前所有同步任务执行完成后,再从事件队列中取出任务依次执行。
其中异步任务又分为 宏任务 和 微任务 ,分别存储在 宏任务队列 和 微任务队列
在每个宏任务执行完成后,都要去微任务队列依次取出微任务进行执行,直到清空微任务队列,然后开始执行宏任务队列中的下一个宏任务,如此循环,这个过程就称之为 事件循环
如何区分宏任务与微任务?
- 微任务是 ES6 语法规定的(被压入 micro task queue)
- 宏任务是由浏览器规定的(通过 Web APIs 压入 Callback queue)
宏任务
微任务
举例说明
// 可以将整个代码片段当做是一个宏任务 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
})
})
执行流程:
- 执行宏任务 mac1 中的同步代码,输入 1、7 ,将 mic1 和 mic2 加入微任务队列
- 依次执行微任务队列中的代码,输出 6、8
- 执行宏任务 mac2 中的同步代码,输出 2、4,将 mic2 和 mic4 加入微任务队列
- 依次执行微任务队列中的代码,输出 3、5
- 执行宏任务 mac3 中的同步代码,输出 9、11,将 mic5 和 mic6 加入微任务队列
- 依次执行微任务队列中的代码,输出 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')
执行流程:
- 执行宏任务 mac1 中的同步代码,输出 d
- 遇到 mac2 ,将其放入宏任务队列
- 执行 async1() ,输出 a
- 执行 async2(),输出 c ,并返回 2
- 此时 await 会阻塞 async2() 的返回赋值,将其后面的代码记为 mic1,加入微任务队列,然后跳出 async1 执行后面的代码
- 执行 new Promise,输出 g,将 mic2 加入微任务队列
- 输出 i ,此时宏任务 mac1 执行完毕,开始执行依次执行微任务队列
- 执行 mic1,输出 b,此时 async1 执行完毕,再将其 then 方法中的任务 mic3 加入微任务队列;执行 mic2,输出 h
- 然后执行 mic3,输出 f
- 最后执行宏任务 mac2,输出 e