手写 Promise(后篇)
前言: 最后的章节终于要来了,不知道有没有人真正跟进下去这个进阶系列。最初我的本意只是想直接从 《手撕 Promise》 开始。但是我想了想,这样的话对一些基础知识不是特别牢固的小伙伴不太友好,于是就创建了该专栏尽量从头开始讲给那些真正想从我的博文中学习到那么一丢丢东西的人。一起加油吧,冲鸭~
阅读本文前需要拥有我们前面的六个进阶任务的通关钥匙🔑 (0/6)
请跟随本文完成你登神长阶的最后一步。
一. 分析 MyPromise 现有的问题
如果你跟进了前篇的知识,那么你目前的代码应该是下面这个样子。
虽然看起来即实现了存储同步数据的功能,又实现了存储异步数据的功能,但是到目前为止,我们的 MyPromise 还是一个假的 Promise 。什么意思呢?
我们先看一下原生的 Promise 存储一个同步数据时的样子。
根据之前的知识,在主线程的 console.log('我在主线程,我应该是第一执行')
肯定是会比 Promise
实例的 then
方法的 console.log
执行的快的,因为一个是在主线程,一个还得在微任务队列里排一会队。我们可以很快得出,在控制台的结果应该如下:
现在我们测试一下我们的刚刚写好的 MyPromise
的情况是什么样子的。如果我们的 MyPromise 是理想状态下的效果, resolve
方法应该会被放入微任务队列。所以下面的代码按照理想情况应该是先输出 console.log(我应该是第一次执行)
这行代码,然后输出数字 1。
然而结果却是:
这该怎么办呢?🤔
二. 微任务的创建
如果你之前认真读过我的《宏任务和微任务》这篇文章,那么你一定知道创建一个微任务其实非常非常简单。没错,就是使用 window 对象身上的 queueMicrotask
函数。
说干就干,我们的数据是在哪读的?你还记得吗?没记起来我提醒你一下,应该是在 then
方法的第一个回调函数 onFulfilled
函数中的吧?那不就非常简单了吗?
我直接在 state==='fulfilled'
的时候,也就是数据填充好以后,我将 onFulFilled
函数从原来的同步执行代码逻辑转变为放入微任务队列去执行。哦哦,对了,别忘了我们 resolve
函数也执行了一个特殊的 onFulfilled
函数。
那么这里同理,把读取数据的任务放入微任务队列里去执行。
这时候我们测试一下,看看是不是我们想的那样。
上面是保存同步数据的情况,我们再测试一下保存异步数据的情况。
下面是控制台输出结果:
- 完美!是我们想达到的效果~
三. 多次调用then方法的结果
- 看似目前我们这个
MyPromise
好像有那味儿了。但是我们分析一下下面的一种情况。我们还是根据原生Promise
来推断。 - 注意! 这里不是
then
方法的链式调用,而是一个Promise
实例多次调用then
方法的写法。(这里不要和链式调用搞混了)
让我们看一下控制台的情况:
可以看到,我们执行了三次 then
方法,它就帮我们执行三次读取数据的操作。然而反过头来看一下我们的 MyPromise
是什么行为。
按照我们理想的效果,控制台应该会在2秒后输出三个 我是 MyPromise
。
ok我们测试一下:
可以看到,我们的控制台在2秒后只输出了一次 MyPromise
。这是怎么回事呢?🤔
四. 分析 Bug 产生的原因
不着急,我们一步一步分析代码的执行顺序。首先会执行 MyPromise
里 executor
函数的代码。
然后代码会执行到 setTimout
,然后会把 resolve
放入宏任务队列里去等待2秒。
ok,接下来该执行下面三个连续的 then
方法。
注意接下来是全文第一个重点: 关键点就在于我们三个 then
是同步代码,一定要搞清楚这回事。明白了这一点,我们就需要去看 MyPromise
类里到底发生了什么情况。
由于我们的 resolve
还在任务队列里排着队,那么在两秒内这时候的 state
百分之一百还是 pending
状态。
可以我们的 then
是要继续走的啊,这里需要接着往下我们看 then
方法里的逻辑。由于我们这时候的 state==='pending'
所以下面的逻辑我们压根不用考虑,只需要考虑第一个 if
语句里的代码即可。
关键点就在于这里这段代码。
我执行的三次 then
在这里相当于执行了三次赋值操作。什么意思呢?
没看懂?没关系,我接下来这种写法你一定可以看明白什么意思。
你觉得控制台会为你分别执行三次吗?
看懂了这个例子,其实你就明白为什么我们的结果只会输出我们最后一次调用的 then
方法的结果了。因为们前面两次都被第三次所覆盖了。
所以我们才会看到下面个结果,只输出了一个 result3
。