Promise 异步同步 面试题

这道题可以说是面试必考了,我在笔试中就遇到过好多次,你们应该都遇到过吧?。。以前拿到这道题时,我整个人都是懵的,看着代码就觉得又长又绕的,最后总是不能完全做对。

题目
解题步骤
思路
答案
总结
为什么需要异步
关于 Promise
微任务 & 宏任务
JS 执行顺序【简单版】
JS 执行顺序【加上 Event Loop】
JS 执行顺序【加上微任务、宏任务】
1.题目
请输出下面的执行结果:

   new Promise(resolve => {
        setTimeout(() => {console.log(0)}, 0)
        resolve()
    }).then(() => {
        setTimeout(() => {console.log(1)}, 0)
    })

    setTimeout(() => {console.log(2)}, 0)

    new Promise(resolve => {
        setTimeout(resolve, 0)
    }).then(() => {
        console.log(3)
        setTimeout(() => {console.log(4)}, 0)
        new Promise(r => r()).then(() => {console.log(5)})
    })
    
    setTimeout(() => {console.log(6)}, 0)
    
    new Promise(resolve => {
        console.log(7)
        resolve()
    }).then(() => {
        console.log(8)
    })

    for(var i = 9; i < 12; i++) { 
        setTimeout(() => {console.log(i)}, 0)
        console.log(i)
    }

先讲原理或结论可能会让你们看得比较枯燥,所以我决定先讲我的解题步骤;然后再去总结 JS 异步执行过程中的一些规则和结论。

结合图片来讲会比较清楚,下面使用到的图片也都是我自己画的。

想起了之前耐心给我讲解过这个题的面试官,他是在纸上边画边给我讲的,讲完的那一瞬间我感觉真的懂了(大部分),原本懵C的我,看到他画的图后思路清晰了不少。

还有,像理解原型、原型链啊这些相对比较抽象的概念也可以试试画图,自己没事就多画几遍,隔段时间忘了就再画几遍,对于我这种笨笨的人是真的挺有效的。看了再多的文章真的不如自己动手做一遍!!就是这个道理昂~

有点啰嗦了叭,那么下面进入正题了!!

2.解题步骤
异步任务里面有两种:宏任务、微任务。下面为了描述方便,我将宏任务简称为 H,微任务简称为 W。

2.1 思路:

图1

图1
注:看每一步的时候,最好结合前面的图片一起对比看奥~

执行顺序如下:

①如上图1,JS 按照从上到下的顺序执行,执行过程中遇到异步任务(包括宏任务和微任务),都会先被加入到任务队列中,再继续向下执行同步代码。

这一步中宏任务 H1-H7 加入到宏任务队列,微任务 W1-W2 加入到微任务队列。

这里要注意,Promise 是会立即执行的,Promise 在创建的时候就会执行,所以 7 会被直接打印出来。

这一步打印: 7 9 10 11

此时,同步代码执行完。

图2

图2
②如上图2,执行微任务 W1。会遇到宏任务 H8,它被添加到宏任务队列末尾。

③执行微任务 W2。这一步打印: 8

这一步执行完后,当前的微任务队列就清空了,下面会开始执行宏任务。

④执行宏任务 H1。这一步打印: 0

这一步没有产生其它的微任务,所以会继续向下执行其它的宏任务。

⑤执行宏任务 H2,这一步打印: 2

图3

图3
⑥如上图3,执行宏任务 H3。微任务 W3 被推入微任务列表。

之前,H3 还未执行时,Promise 中的 resolve 函数也未执行,Promise 处于 pending 状态,所以 then 中的函数是不会被推到微任务队列中的;

而 H3 执行后,resolve 函数也会执行,Promise 的状态由 pending 变为 fulfilled,then 才会被 Promise 调用。

注意:

1.只有 Promise 调用了 then 的时候,then 中的函数才会被推入到微任务队列中;
2.而 Promise 调用 then 的前提是 Promise 的状态为 fullfilled

Promise 这一块不清楚的话,可以去阮一峰的 ES6 中补一下奥~

图4

图4
⑦如上图,执行微任务 W3。这一步打印: 3

宏任务 H9 和 微任务 W4 分别加入到宏任务队列和微任务队列。

⑧执行微任务 W4,这一步打印: 5

同样地,和 ③ 一样,这一步执行完后,当前的微任务队列就清空了,下面会开始执行宏任务。

注意:

1.当前微任务队列清空后才会继续执行宏任务;
2.而宏任务是每执行一个,就去看下微任务队列是否有内容,有的话就挨个执行微任务,直到将微任务队列清空,才会再执行下一个宏任务。

⑨执行宏任务 H4,这一步打印: 6

⑩执行宏任务 H5 H6 H7,这一步打印: 12 12 12
这里一定要注意!!容易搞错。for 循环了 3 次,会产生 3 个宏任务。

⑪执行宏任务 H8,这一步打印: 1

⑫执行宏任务 H9,这一步打印: 4

哇~到此,就执行完了!!

2.2 答案:
所以最后执行结果为:7 9 10 11 8 0 2 3 5 6 12 12 12 1 4

3.总结
3.1 为什么需要异步

JS 是单线程的,同一时间只能做一件事
JS 和 DOM 渲染共用一个线程,因为 JS 可修改 DOM 结构,当 JS 执行时 DOM 渲染要停止,DOM 渲染时 JS 也要停止
遇到等待(如网络请求,定时任务等),不应该被卡住
同步会阻塞代码执行,而异步不会阻塞代码执行
3.2 关于 Promise 【重要】

Promise 解决了什么问题
主要解决了回调地狱的问题。
三种状态
pending(进行中)、fulfilled(已成功)和 rejected(已失败)
状态变化
只有两种情况:
①pending -> fulfilled(成功了)
②pending -> rejected(失败了)
还要注意:变化不可逆!!
状态的表现【重要重要】
①pending 状态,不会触发 then 和 catch
②fulfilled 状态,会触发后续的 then 回调函数
③rejected 状态,会触发后续的 catch 回调函数
then 和 catch 对状态的影响
then 正常返回 fulfilled,里面有报错则返回 rejected ;
catch 正常返回 fulfilled(注意!!),里面有报错则返回 rejected。
这里对 Promise 只是做了一个简单的总结,详细的我之后打算再专门写一篇文章(先挖个坑啦)。

3.3 微任务 & 宏任务

常见的宏任务、微任务分别有哪些
宏任务:setTimeout、setInterval、Ajax、DOM事件
微任务:Promise.then()、async/await
微任务和宏任务的区别
①宏任务:DOM 渲染后触发
②微任务:DOM 渲染前触发
③微任务执行时机比宏任务要早
注意:
微任务执行时会放到一个单独的 micro task queue(微任务队列)中,和宏任务队列是分开的。
原因:微任务是 ES6 语法规定的,宏任务是由浏览器规定的
宏任务、微任务和 DOM 渲染的关系
Call Stack 清空
执行当前的微任务
尝试 DOM 渲染
执行宏任务
3.4 JS 执行顺序【简单版】

按照从上到下的顺序,一行一行执行
如果某一行执行报错,则停止下面代码的执行
先把同步代码执行完,再执行异步代码
3.5 JS 执行顺序【加上 Event Loop】

同步代码会一行一行放在 Call Stack 中执行
遇到异步,会先记录下,等待时机(定时器、网络请求等)
时机到了,就移动到 Callback Queue 中
如果 Call Stack 为空(即同步代码执行完),Event Loop 开始工作
轮询查找 Callback Queue,如果其中有内容,则移到 Call Stack 中执行
如下图(图5):简单画了个图…画的不太好,别见怪~~

图5

图5
3.6 JS 执行顺序【加上微任务、宏任务】

1、同步代码会一行一行放在 Call Stack 中执行
2、遇到异步,会先记录下,等待时机(定时器、网络请求等)
3、时机到了,就移到队列中
                     3.1 宏任务会经过 Web API 后,再移到宏任务队列中。
                          如:执行代码时遇到 setTimeout,会先将它扔给 Web API 中的 timer,当定时时间到了(即时机到了)之后,再被推到宏任务队列中。
                     3.2 微任务不会经过 Web API,会直接移动到微任务队列 micro task queue 中。
                          如:执行代码时遇到了 Promise,会将 then 中内容移到微任务队列中。
4、如果 Call Stack 为空(即同步代码执行完),Event Loop 开始工作
5、先去查找微任务队列,如果有内容,则推到 Call Stack 中执行
6、当微任务队列清空后,再去查找宏任务队列,如果有内容,则推到 Call Stack 中执行
7、如上步骤循环

图6
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值