异步(async/Promise/setTimeout)面试题解析 - Go语言中文社区

异步(async/Promise/setTimeout)面试题解析


前言

以下是一篇异步 (async/Promise/setTimeout) 面试题。看到网上不少解答,有的非常详细,同时也很啰嗦。有的分析得太简单,不免过于粗糙。本文试图以通俗易懂的语言来剖析一下这道面试题,还是不懂的话,来找我。在开始之前先介绍几个名词。

宏任务(macrotask):
宏任务指执行栈中待执行的任务(主线程上的同步任务)。
宏任务包括 script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)。
宏任务与页面渲染的关系是:
第一次宏任务执行到结束 => 开始渲染页面 => 第二次宏任务执行到结束

微任务(microtask):
微任务指当前任务执行之后立即执行的任务。
微任务包括 Promise.then、MutaionObserver、process.nextTick(Node.js 环境)。
微任务与页面渲染的关系是:
第一次宏任务执行到结束 => 微任务执行到结束 => 开始渲染页面 => 第二次宏任务执行到结束(因此微任务会在第二次宏任务执行之前执行

事件运行机制:

  1. 执行一个宏任务;
  2. 遇到微任务,放到微任务列队;
  3. 宏任务执行完毕,执行微任务列队中的任务;
  4. 微任务执行完毕后,GUI 线程接管,开始渲染页面;
  5. 渲染完成后,JS线程继续接管,开启下一个宏任务。

题目:

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');

答案:
在这里插入图片描述

一、Promise 函数

new Promise((resolved)=>{
            console.log(1);
             resolved();
        }).then(()=>{
            console.log(2);
        })

Promise 函数执行顺序比较简单,Promise 内的任务为立即执行任务,也可以理解为同步任务。而 then 方法中的任务是微任务,会放在微任务列表中。所以以上执行顺序是1,2。

二、async 函数

async 函数主要通过 await 关键字实现异步任务同步写法。如果没有 await 关键字,那么 async 函数跟普通函数没区别。

案例1:

   async function async1() {
        console.log('1');
        await new Promise(function (resolve) {
            setTimeout(function () {
                resolve(888);
                console.log('2');
            },1000)
        });
        console.log('3');
    }
    async1()

以上函数如果没有 await 关键字,输出结果应该是1,3,2。加了 await 之后虽然有异步,但是也是按同步顺序执行,结果为1,2,3。

案例2:

   async function async1() {
        console.log(1);
        await async2();
        console.log(3);
    }
    async function async2() {
        console.log(2);
    }
    async1();
    new Promise(function (resolved) {
        console.log(4);
    })

按照案例1的理解,async1()函数执行之后输出顺序为1,2,3。后面有个 Promise 函数,函数内有立即执行代码 console.log(4)。那么结果是不是 1,2,3,4?
async 是 promise 和 generator 的语法糖,所以 async1 方法等同于

   async function async1() {
        console.log(1);
        new Promise((resolved)=>{
             async2()
             resolved();
        }).then(()=>{
            console.log(3);
        })
    }
    async function async2() {
        console.log(2);
    }

这里可以看出 console.log(3) 在 Promise.then 中,是一个微任务。所以在立即执行任务 console.log(4) 之后执行。所以以上执行顺序为1,2,4,3。

三、setTimeout 函数

setTimeout 函数是一个宏任务,需要等到 script 整体的宏任务执行完成之后再执行。

<script>
console.log(1);
setTimeout(function () {
    console.log(2)
})
</script>

<script>
    console.log(3)
</script>

打印结果为 1,3,2。第一个 script 是一个宏任务,setTimeout 是定时器类型的宏任务。而第二个 script 跟第一个是同一个列队。所以先执行 script 列队中的宏任务,再执行定时器宏任务。

四、解析

根据以上的说明和案例,该面试题剖析如下:

  1. 题中有两个宏任务,第一个是 script 标签包含的整体脚本,第二个是 setTimeout 定时器宏任务。所以定时器中的任务 console.log('setTimeout') 肯定是最后执行。

  2. async 中和 Promise 中的任务都是立即执行任务,即 console.log('async1 start')console.log('promise1'),可以理解为是同步任务。await是一个让出线程的标志。await后面的表达式也会立即执行,即 console.log('async2') 也会同步执行。而 await 后面的代码会加入到微任务列队中。此时微任务列队中有 console.log('async1 end')

  3. 在Promise.then() 函数中有微任务 console.log('promise2')

  4. 整理一下以上面试题。
    (1)同步或者立即执行任务有:
    console.log(‘script start’);
    console.log(‘async1 start’);
    console.log(‘async2’);
    console.log(‘promise1’);
    console.log(‘script end’);
    (2)微任务有:
    console.log(‘async1 end’);
    console.log(‘promise2’);
    (3)除了第一个script 宏任务,第二个宏任务是:
    console.log(‘setTimeout’);

    所以执行顺序为:
    在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44135121/article/details/100116944
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢