javaScript 进阶之路 --- 《手写 Promise(前篇)》
手写 Promise
前言: 该来的还是来了,经过完成我们前面的四个支线任务
终于到了我们的主线任务 ---《手写 Promise》。对于前端来讲,当你完全理解 Promise 的设计思路,那么你会对你之前所了解的 JS 世界,有一个全新的认知。
不知各位是否有过下面这样的想法:
--“我感觉我现在知识储备差不多了,接下来如何进阶呢?”
其实这个问题谁都无法告诉你准确答案。只有当你在某一天掌握了你之前不知道的知识点,并且完全领悟它的时候。你脑子里就会突然有种 “知识升华” 的感觉。(我也无法准确描述那种感觉,就好像突然悟了一样)脑子里会瞬间把之前所有知识串联起来,各个知识点不再是一个一个的碎片🧩。
这种感觉就是在我某一天读懂 JS 回调函数真正想表达出的思想,并且手写出 Promise 涌现的。当你某天有过一次这样感觉后,你就不会再去问别人关于如何进阶这种问题了,因为那时候的你,其实已经完成了当前知识的进阶,当你回过头看之前的代码时,你会发现不一样的世界,是那种拨云见日的感觉。
⚠️注意:本文的内容需要你对回调函数和宏任务微任务有比较清晰的认识,请不太懂的小伙伴在家长的陪同下认真观看
一. 文件准备
本文需要你准备的文件非常简单,随便在你的目录文件夹下创建一个 myPromise.ts 文件,对 .ts 不太熟悉的小伙伴不需要担心,本篇用到 ts 的相关内容很少。

二. 实现 MyPromise类 的构造器函数
-
首先我们定义一个叫做
MyPromise的类。在接下来我会顺着原生的 Promise 一步一步帮你去理解 Promise 的实现思想。
-
在上一章内容我们了解了,Promise 类会接收一个叫做
executor的函数来初始化我们的 Promise 类的实例。
ok,并且我们还特别强调了,executor就是一个普普通通的函数而已。那么我们就可以像下面这样,在MyPromise的constructor函数内定义一个形参,来准备接收在初始化时传递给我们的实参数。
我相信聪明的你一定可以看出来,我们其实在 new一个 Promise 实例的时候,传递给Promise类的函数就是我们刚刚定义的这个executor函数的实参。
-
OK,我们继续。我们还知道 executor 函数会被传递两个参数。这两个参数分别是一个叫做
resolve和reject的函数!注意,它们两个是函数!!所以我们就可以像下面这样写。
好像飘红了?是的,因为在 MyPromise结构体内,还没有这两个函数。那怎么办呢?🤔,这还不简单?没有我们就自己造呗~ -
可以看到我们自己定义了两个函数,好像还是飘红...
这里就需要用到 this 关键词,来告诉 executor我要传递的是类本身的方法。所以正确的方法应该下面这样。
至此,我们的 executor 函数就已经写好了。
三. 实现 resolve,reject 函数
-
这里我们先实现
resolve,别着急。我们先看原生Promise是怎么使用的。
我们可以得知,resolve 函数可以被传递一个参数,所以我们可以更进一步得出。
我们先来测试一下我们的思路是不是对的。我们先 new一个实例保存一个数据看看。
嗯...看来有点那味道了。🍦 -
同理
reject函数也是这种写法。到这一步,我想你的MyPromise类应该长这个样子。
四.实现 then 方法
-
我们先回忆一下,原生的
Promise读取数据的时候,是在实例的then方法上读取的。这里我们就需要提供一个变量去接收resolve传递的值。并且需要在MyPromise类中提供一个then方法,去读取传递过来的数据。
-
这里先停一下,我们想一想。我们的
result是不希望被实例引用的。什么意思呢?如果我们按照上面的写法,是会引发这样的错误的。
实例竟然可以直接去引用这个 result ,这是我们不希望看到的。
这里需要用到的知识是:假如我们不希望实例调用某个属性,方法也很简单,只需要在前面加上一个 #号即可。
-
接下来是本文的第一个重点。
then函数该怎么设计?我们先看原生Promise实例的身上.then方法是怎么使用的。
由之前的知识我们可以知道,then方法也是接受两个回调函数作为参数的。并且第一个回调函数的参数会被传递resolve保存的值,第二个回调函数的参数会被传递reject保存的值。 -
ok,那我们先不考虑那么多,直接先给
then函数传两个参数,这两个参数也是两个函数。第一个参数我们就起一个叫onFulfilled的函数 ,它对应着resolve保存的值。第二个参数我们就叫onRejected吧,它也应该是一个函数。于是我们就可以补充then函数的内容,它会被传递我们通过reject保存的那个结果。
下面的代码应该是你目前写出来的样子。

五. MyPromise 的三种状态
-
等等,我们好像忘了一个很关键的东西,Promise 的三种状态!!!还记得吗?Promise 在初始化的时候是有三个状态的,分别是
pending,fulfilled和rejected。这三种状态分别影响着then函数中我们取到的值。 -
知道这一点,我们马上就应该想到,我需要再定义一个变量,来存放这三个状态值。
并且还有一点,pending状态是会在初始化,也就是还没调用resolve或者reject函数之前的状态。 所以我们就需要在constructor构造函数调用executor之前将state的状态信息改为pending,对应就是下面这行代码的意义。
-
并且我们可以推算出,在
resolve函数内,和reject函数内需要分别修改状态值为fulfilled和rejected,就如同下面的写法一样。
-
由于我们
then函数会根据状态去做相应传递的参数不同,所以理所当然的需要修改为下面的写法。
-
下面应该是你目前的代码。

六. 创造一个 MyPromise 实例
- 目前看起来好像我们的逻辑非常严谨对吧,我们别着急往下写,我们自己去调用一下看看是什么样子。我们先自己
new MyPromise生成一个实例看看。
按照我们的推算,控制台应该会出入一个字符串韩振方。 我们看一下结果:
什么情况?竟然报错了?别着急,我们分析一下错误。
Uncaught TypeError: Cannot set properties of undefined (setting '#result')
这里给出的错误信息是,我们不能给 undefined 设置 #result 的属性。
怎么回事啊?我们怎么在给 undefined 设置属性?我们不是在给 MyPromise 实例设置属性吗?我还就不信了,我非得打印一下看看,我们在 resolve 里面打印一下 this看看到底这个 this 是谁 。
看一下控制台:
emm...好像还真是 undefined。
-
到底怎么回事呢?🤔 其关键点在于下面这段代码。
在这里我们看似在引用实例自身去调用 resolve,实际上在这里调用者的其实是window对象。不过因为我们在 Class 类里面是默认开启严格模式的,如果丢失了 this ,并不会将 this 默认指向全局对象window。
-
所以我们需要干一件非常非常重要的事情,没错,就是绑定 this。
因为我们 constructor 函数里的 this总是会指向实例本身,所以我们需要在 调用resolve函数和reject函数之前,需要在excutor的参数提前绑定好this才可以。所以我们现在的代码应该是这个样子:
-
让我们再次调用一下我们自己的 MyPromise ,来看一下现在能不能成功读取我们保存的数据。
可以看到,我们已经成功读取了我们通过then方法读取到了resolve传递过来的数据。 -
到这里你可能好奇,为什么
then方法不用绑定this。因为你别忘了我们的then是怎么去调用的。
可以看到,我们的 then是通过对象点一个属性名去调用的,那么它的 this 百分百就是 data 实例对象。它是不需要我们考虑 this 指向问题的。
总结
在本文主要引导大家构造好一个 Promise 的骨架,大家可以先消化一下,在下一篇内容我会更深入的讲解其中的细节。🎁
转载自:https://juejin.cn/post/7176639726923841591