在上一篇文章(面试官:聊一下 Event - Loop)中,我们探讨了 事件循环(Event Loop)的引入、概念及执行过程。今天的内容是之前的进阶,如果对今天的内容不太熟悉的掘友可以先看一下往期的作品。
今天我们将更进一步,深度剖析一道事件循环常考的面试题。
话不多说,先上题。
大试牛刀
请写出以下代码的打印顺序:
js
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
【答案】(点击展开) script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
我们先来回忆一下上一篇文章的知识:
事件循环的步骤
1.进入到
script
标签,script
隶属于宏任务,进入第一次事件循环2.遇到同步代码,立即执行
3.遇到宏任务,放入到宏任务队列里;遇到微任务,放入到微任务队列里
4.执行完所有同步代码,执行微任务代码
5.微任务代码执行完毕,寻找下一个宏任务
6.重复步骤1
这道题中出现了ansyc/await ,如果不清楚的同学可以展开看我的介绍,大佬则可选择跳过。
什么是 ansyc/await
对 ansyc/await 的介绍(点击展开)
什么是 ansyc/await
async/await
是 JS 中用于处理异步操作的一种语法糖,它基于 Promise
,使得异步代码的编写和阅读更加简洁和清晰。async
函数用于定义一个返回 Promise
对象的异步函数,而 await
用于等待一个 Promise
对象的解决。简单点说,就是在Promise
的基础上做了点优化,具体有哪些我们继续往后看。
现在有以下异步任务:
js
function A() {
setTimeout(()=>{
console.log('异步A完成');
},1000)
}
function B() {
setTimeout(()=>{
console.log('异步B完成');
},500)
}
function C() {
setTimeout(()=>{
console.log('异步C完成');
},100)
}
A()
B()
C()
正常调用这个三个函数,得到的打印结果为:
异步C完成
异步B完成
异步A完成
如果我们现在就是先要打印顺序为 A
、B
、C
,那么我们一般会使用Promise(详情见------面向小白编程:Promise 的浅入深出)
js
function A() {
return new Promise((resolve, reject)=> {
setTimeout(()=>{
console.log('异步A完成');
resolve()
},1000)
})
}
function B() {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log('异步B完成');
resolve()
},500)
})
}
function C() {
setTimeout(()=>{
console.log('异步C完成');
},100)
}
A()
.then(()=>{
return B()
})
.then(()=>{
C()
})
通过Promise.then()
方法我们能轻易控制异步任务的打印顺序。
但是今天我们又要介绍一种新的处理异步的方法------ansyc/await
,如何使用呢?
1. 定义异步函数: 使用 async
关键字声明一个异步函数。异步函数会返回一个 Promise
对象。
js
async function foo() {
// 异步操作...
}
2. 使用 await
等待异步操作完成: 在 foo
函数中,通过 await
关键字等待异步函数 A
、B
、C
完成。await
会暂停 foo
函数的执行,直到对应的异步操作完成。
js
function A() {
return new Promise((resolve, reject)=> {
setTimeout(()=>{
console.log('异步A完成');
resolve()
},1000)
})
}
function B() {
return new Promise((resolve, reject)=> {
setTimeout(()=>{
console.log('异步B完成');
resolve()
},500)
})
}
function C() {
return new Promise((resolve, reject)=> {
setTimeout(()=>{
console.log('异步C完成');
resolve()
},100)
})
}
async function foo() {
await A();
await B(); // 等待异步A完成
await C(); // 等待异步B完成
}
foo();
3. 最终调用 foo
函数就可以得到打印结果:
异步A完成
异步B完成
异步C完成
易错点
promise
本身是一个同步的代码,只有它后面调用的then()
方法里面的回调才是微任务await
右边的表达式会立即执行,表达式之后的代码,即表达式下方的代码会被阻塞,被推入微任务队列。
步骤分析
拿到这段代码,开始执行script
宏任务,即进入第一次事件循环。遇到同步代码,立即执行;遇到宏任务,放入到宏任务队列里;遇到微任务,放入到微任务队列里。
当执行到 async1()时,await async2()立马执行打印async2 end ,而await后面的任务进入微任务队列。最终执行栈、宏任务队列和微任务队列所示如下:
同步代码执行完毕,开始执行微任务:
微任务队列为空,开始执行下一个宏任务,开始下一次事件循环:
这次循环中只有一个同步任务console.log('setTimeout'),直接打印。
综上所述,打印顺序为:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
至此,这道面试题已经解决,如果你有任何疑惑欢迎评论区留言!
最后
看到这里,我相信你已经可以轻易回答事件循环相关的面试题了,愿你在面试的道路上取得优异的成绩,同时也在技术的海洋中扬帆起航,创造出更加精彩的前端世界。加油!
已将学习代码上传至 Github,欢迎大家学习指正!
技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 "点赞 收藏+关注" ,感谢支持!!