JavaScript: 一战吃透Promise精修版

缘起

曾2017年大学毕业,通过校招来到了一家不错的大厂工作,若按当时的校聘,可真没有前端相关的面试笔试题呀,基本侧重方面都是java、C++、C#、Sql相关的考题。因为这些在大学里是必修课,也更能反映学生的学习能力。所以后面工作之后,基本就是实战居多,没有过多了解外面公司的面试笔试要求,和甄选应聘者的条件。在公司的第一年,就开始坚定地往前端方向的走下去了,因为我觉得,与其枯燥无味地研究后端的逻辑代码,还不如能直观看到视觉效果的前端的成就感来得更加爽快(并不是鄙视后端,只是个人的爱好,而且相对而言上手迅速,是不是也有很多前端的掘友也跟我一样的想法

有的人说,三年就能把前端学到了天花板,我想这跟个人的眼界有关;你以为的天花板,和别人以为的天花板真的是一回事吗?

当我开始考虑晋升和挑战自我(跳槽)的时候,就想本着两年前端经验的实战作品,去应聘薪酬条件更高的公司,结果碰了一脸灰,本以为有了工作经验之后,笔试题可以直接跳过了。尴尬的是,摆在我面前的就有多道Promise的改编和升华的题目。当时真的一脸懵逼,虽说在项目中几乎哪里都用到。但是,我在问自己,我自己真的已经掌握它了吗。会用不等于理解,当然理解了,同样也不一定会用。我们需要通过实战中更深刻地去理解,这样,出题人,不论如何改编题目,我们都能通过理解的知识点,来解题。 题目变的是形式,而其本是同一类型的题。

后面我就针对这个题型,做了深度总结,同时也分享给大家,不对的可以建议建议,非常感谢!

从下面几个方面,做下精修:

1. Promise相关概念-白话文篇
2. 微任务、宏任务与Event-Loop-长话短说篇
4. 微任务、宏任务混合考题-综合篇

精修

Promise相关概念-白话文篇

白话文的意思就是不想通过以下图中那么官方的讲解,结果换来掘友的一字好评==“就我一个人看半天不知道说的啥吗”,结果引来一波掘友+1,哈哈哈😂,所以后面我换了一种通俗接地气的说法,给一一理解的

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。我想,如果也能拥有一份promise的爱情,那真的是三世修来的福气啦哈哈哈😂

(2)一旦状态改变,就不会再变,(想想狗的忠诚)任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就会凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的,往事不可追。

image.png

上面所说的两点需要你有对异步有过了解,如果没有比较清晰的认识,我这里也为入门级的前端掘友做一下补充:异步事件

异步事件的概念

注册一个回调函数,比如说发一个网络请求,我们告诉主程序等到接收到数据后通知我,然后我们就可以去做其他的事情了。

然后在异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,可以去执行。

比方说打了个车,如果司机先到了,但是你手头还有点儿事情要处理,这时司机是不可能自己先开着车走的,一定要等到你处理完事情上了车才能走

image.png

微任务、宏任务与Event-Loop-长话短说篇

event loop它的执行顺序:

1. 一开始整个脚本作为一个宏任务执行
2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
3. 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
4. 执行浏览器UI线程的渲染工作
5. 检查是否有`Web Worker`任务,有则执行
6. 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

微任务包括:

`MutationObserver`
`Promise.then()或catch()`
`Promise为基础开发的其它技术,比如fetch API`、`V8`的垃圾回收过程
`Node独有的process.nextTick`

宏任务包括

scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

注意⚠️:在所有任务开始的时候,由于宏任务中包括了script,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如setTimeout)将被放到下一轮宏任务中来执行。

补充说明:

注:非入门级掘友请跳过以下描述

如果上面所说的有点晦涩难懂,这里就把抽象具体化:
宏任务和微任务的过程,就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。

因为柜员同时职能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的,当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为说有一个任务队列在这,里边是当前银行中所有排号的客户。
任务队列中的都是已经完成的异步操作,而不是说注册一个异步任务就会被放在这个任务队列中,就像在银行中排号,如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号

而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“最近P2P爆雷有点儿多,是不是要选择稳一些的理财呢”,然后告诉柜员说,要办一些理财的业务,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。

在当前的微任务没有执行完成时,是不会执行下一个宏任务的

那么event-loop又是个啥东西?上边一直在讨论 宏任务、微任务,各种任务的执行。
但是回到现实,JavaScript是一个单进程的语言,同一时间不能处理多个任务,所以何时执行宏任务,何时执行微任务?我们需要有这样的一个判断逻辑存在。

每办理完一个业务,柜员就会问当前的客户,是否还有其他需要办理的业务。(检查还有没有微任务需要处理)
而客户明确告知说没有事情以后,柜员就去查看后边还有没有等着办理业务的人。 (结束本次宏任务、检查还有没有宏任务需要处理)

这个检查的过程是持续进行的,每完成一个任务都会进行一次,而这样的操作就被称为Event Loop。 (这是个非常简易的描述了,实际上会复杂很多)

而且就如同上边所说的,一个柜员同一时间只能处理一件事情,即便这些事情是一个客户所提出的,所以可以认为微任务也存在一个队列。

微任务、宏任务混合考题-综合篇

接下来我们就来验证下我们刚get到的知识,是否已经理解通透。

异步题1

这道题主要考察的是事件循环中函数执行顺序的问题,其中包括asyncawaitsetTimeoutPromise函数。下面来说一下本题中涉及到的知识点。

//请写出输出内容
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');


/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

以上的结果都可以通过下面码上掘金去验证结果;
jcode

异步题2(改编)

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

jcode

可以先自己看看输出顺序会是什么,下面来公布结果:

script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout

在第一次macrotask执行完之后,也就是输出script end之后,会去清理所有microtask。所以会相继输出promise2 async1 endpromise4,其余不再多说。

异步题3(改编)

在第二个变式中,我将async1中await后面的代码和async2的代码都改为异步的,代码如下:

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
setTimeout(function() {
 	console.log('setTimeout2')
 },0)
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout3');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

可以先自己看看输出顺序会是什么,下面来公布结果:

script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1

在输出为promise2之后,接下来会按照加入setTimeout队列的顺序来依次输出,通过代码我们可以看到加入顺序为3 2 1,所以会按3,2,1的顺序来输出。

异步题4(改编)

变式三是我在一篇面经中看到的原题,整体来说大同小异,代码如下:

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}
console.log('script start')
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
    console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})
promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

无非是在微任务那块儿做点文章,前面的内容如果你都看懂了的话这道题一定没问题的,结果如下:

script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout

如果看到这里的掘友,我相信你已经基本掌握了上面的内容,可以去网上再挑选一些题目再小试牛刀,验证自己的成功吧,嘿嘿😁

预告

后面我会针对入门级写个总结由浅入深去一一加深我们对宏任务和微任务的理解,欢迎点赞关注+收藏谢谢!
敬请期待!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值