【进阶】Event Loop 常考面试题

在上一篇文章(面试官:聊一下 Event – Loop)中,我们探讨了 事件循环(Event Loop)的引入、概念及执行过程。今天的内容是之前的进阶,如果对今天的内容不太熟悉的掘友可以先看一下往期的作品。

今天我们将更进一步,深度剖析一道事件循环常考的面试题。

话不多说,先上题。

大试牛刀

请写出以下代码的打印顺序:

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的基础上做了点优化,具体有哪些我们继续往后看。

现在有以下异步任务:

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完成

如果我们现在就是先要打印顺序为 ABC,那么我们一般会使用Promise(详情见——面向小白编程:Promise 的浅入深出

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 对象。

async function foo() {
  // 异步操作...
}

2. 使用 await 等待异步操作完成:foo 函数中,通过 await 关键字等待异步函数 ABC 完成。await 会暂停 foo 函数的执行,直到对应的异步操作完成。

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完成


易错点

  1. promise本身是一个同步的代码,只有它后面调用的then()方法里面的回调才是微任务
  2. await右边的表达式会立即执行,表达式之后的代码,即表达式下方的代码会被阻塞,被推入微任务队列。

步骤分析

拿到这段代码,开始执行script宏任务,即进入第一次事件循环。遇到同步代码,立即执行;遇到宏任务,放入到宏任务队列里;遇到微任务,放入到微任务队列里。

【进阶】Event Loop 常考面试题

当执行到 async1()时,await async2()立马执行打印async2 end ,而await后面的任务进入微任务队列。最终执行栈、宏任务队列和微任务队列所示如下:

【进阶】Event Loop 常考面试题

同步代码执行完毕,开始执行微任务:

【进阶】Event Loop 常考面试题

微任务队列为空,开始执行下一个宏任务,开始下一次事件循环:

【进阶】Event Loop 常考面试题

这次循环中只有一个同步任务console.log(‘setTimeout’),直接打印。

综上所述,打印顺序为:

script start

async2 end

Promise

script end

async1 end

promise1

promise2

setTimeout

至此,这道面试题已经解决,如果你有任何疑惑欢迎评论区留言!

最后

看到这里,我相信你已经可以轻易回答事件循环相关的面试题了,愿你在面试的道路上取得优异的成绩,同时也在技术的海洋中扬帆起航,创造出更加精彩的前端世界。加油!

已将学习代码上传至 Github,欢迎大家学习指正!

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!

原文链接:https://juejin.cn/post/7312731537589125160 作者:阳阳羊

(0)
上一篇 2023年12月17日 上午10:37
下一篇 2023年12月17日 上午10:47

相关推荐

发表回复

登录后才能评论