You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classPROMISE{constructor(executor){if(typeofexecutor!=='function'){thrownewTypeError(`Promise resolver ${executor} is not a function`);}// 初始状态this.status='pending';// 初始值this.value=null;// class 内部默认是严格模式,所以需要绑定 thisexecutor(this.resolve.bind(this),this.reject.bind(this));}then(){}resolve(value){// 状态保护if(this.status!=='pending'){return;}// 改变状态, 赋值this.status='fulfilled';this.value=value;}reject(reason){// 状态保护if(this.status!=='pending'){return;}// 改变状态, 赋值this.status='rejected';this.value=reason;}}
到这里,我们就差不多实现了规范 2.1。
3.then
then 可以接受两个函数,会在状态改变之后异步执行,根据规范 2.2.1,如果它们不是函数,就忽略。then 的异步本是一个微任务,这里用宏任务 setTimeout 就将代替一下(如果你想了解微任务、宏任务的知识,欢迎点这里查看我写的关于 Event Loop 的文章)。
前言
在前端开发中,
Promise
是一个特别重要的概念,很多的异步操作都依赖于Promise
。既然在日常中和它打过那么多的交道,那么我们来自己实现一个Promise
,加深对Promise
的理解,增强自己的JavaScript
功力。本次在实现
Promise
的同时会使用jest
编写测试用例,以保证实现过程的正确性。如果想看测试框架的搭建或者完整实现的,可以点击我的github 仓库进行查看,如果你喜欢,欢迎 star,如果你发现我的错误,欢迎提出来。
A+规范
这是一份开放、健全且通用的
Promise
实现规范。由开发者制定,供开发者参考。这里是官方规范,照着官方规范去实现,就可以写一个属于自己的、符合标准的
Promise
。实现
话不多说,我们来开始根据 A+ 规范实现
Promise
。规范的第一节是对一些术语的表达,并无实际功能无需实现。
1. 声明 Promise 类
Promise
是一个类(JavsScript
的类是用函数实现的,只是一个语法糖),它必须接受一个函数,否则报错;它还有一个then
方法。测试用例如下:
测试用例我后面不再列出来了,有兴趣的可以去我的 github 仓库查看。
2.状态维护
Promise
有三种状态:请求态(pending
)、完成态(fulfilled
)和拒绝态(rejected
)。Promise
一开始是请求态,它可以转为另外两种状态(只允许改变一次),它会在状态改变的时候得到一个值。到这里,我们就差不多实现了规范 2.1。
3.then
then
可以接受两个函数,会在状态改变之后异步执行,根据规范 2.2.1,如果它们不是函数,就忽略。then
的异步本是一个微任务,这里用宏任务setTimeout
就将代替一下(如果你想了解微任务、宏任务的知识,欢迎点这里查看我写的关于Event Loop
的文章)。这里的情况是期待执行
then
函数时,Promise
的状态已经得到了改变。如果
Promise
的执行函数是一个异步函数,执行then
的时候,Promise
的状态还没得到改变,那么就需要把then
接受的两个函数保存起来,等到resolve
或reject
的时候执行,这里也要异步执行。这样一来,规范的 2.2.2 和 2.2.3 就都实现了。
而且由于
then
里面的onFulfilled
和onRejected
都是异步执行的,所以它也满足规范 2.2.4,它会在Promise
的代码执行之后被调用。根据规范 2.2.5,
onFulfilled
和onRejected
调用时也不存在this
,所以用.call
调用,指定undefined
为this
。规范 2.2.6,如果
then
执行之前Promise
已经改变了状态,那么直接执行多个then
。否则将then
的函数参数存在callbacks
数组中,后面依次调用,实现规范 2.2.6。4. 链式操作
Promise
有一个then
方法,then
之后还可以then
,那么让then
返回一个Promise
即可,根据规范 2.2.7,我们也必须让then
返回一个Promise
。根据规范 2.2.7.1,无论是
onFulfilled
和onrejected
,它们返回的值都会被当做then
返回的新的Promise
的resolve
的值成功处理。根据规范 2.2.7.2,如果
onFulfilled
和onRejected
抛出了一个错误e
,那么会被当做then
返回的新的Promise
的reject
的值失败处理。规范 2.2.7.3 和 2.2.7.4 表示如果
then
的onFulfilled
和onRejected
不是函数,那么新的Promise
会用上一个Promise
成功(resolve
)或失败(reject
)的值继续成功或失败,也就是会继承上一个Promise
的状态和值。举一个例子
因为第一个
then
的参数不是函数,那么会发生穿透传递,所以后一个then
接受的两个参数 function A 和 function B,会根据最前面那个Promise
的状态和值来进行调用。也就是上面的代码其实和下面的代码执行结果一样。
好的,让我们来实现这个规范。
其实很简单,你上一个
Promise
如果是resolve
时,那么我用then
的Promise
也resolve
,且值不变,如果是reject
,那么then
的Promise
也reject
,且值不变。这里稍微一点点绕,希望你把这里仔细看,完全搞明白。
其实这里可以简化一下,变成下面这种。
这样,
Promise
的链式操作就完成了。5.实现 2.3.1
接下来我们继续看规范 2.3 的部分。
如果
Promise
和resolve
或者reject
调用的值是同一个,那么应该使Promise
处于拒绝(reject
)态,值为TypeError
。代码如下:
6.实现 2.3.3
规范 2.3.2 是 2.3.3 情况的一个子集,我们直接实现 2.3.3 就可以了。
规范 2.3.3 说了那么多,其实就是在
resolve
和reject
中添加下面几行代码。关于规范 2.3.3.2,我理解的是,并不是说 x.then 是一个异常,而是在取值的过程中发生了一个异常,代码表达如下:
由于取值 x.then 的过程中抛出了一个异常,被
constructor
中的try catch
捕捉到了,执行reject
,这里就无需做处理了。规范 2.3.4 不用特殊实现,说的就是正常情况。
7.完整实现
到这里就把 A+ 规范走了一遍,实现的
Promise
如下:其中重复的代码我在这里就不抽离出来了,这样方便阅读。
8.静态方法
像
resolve
、reject
、all
、race
这几个静态方法其实不属于 A+ 规范中,我这里也顺带实现一下。resolve
和reject
类似,接受一个值,返回一个Promise
。如果接受的值是一个Promise
,那么就继承该Promise
的状态和值。all
是接受一个Promise
数组,返回一个Promise
。这里定义一个
results
数组,然后遍历Promise
数组,每resolve
一个Promise
,就像results
加入一个resolve
的值,如果results
的长度与Promise
数组的长度相同,那么说明全部的Promise
都resolve
了,那么all
返回的Promise
就resolve
这个数组。另外,只要
Promise
数组中有一个Promise
转为了reject
,那么all
返回的Promise
也reject
掉。race
也是接受一个Promise
数组,返回一个Promise
。只有
Promise
数组中有一个Promise
resolve
或者reject
了,那么race
返回的Promise
也resolve
或者reject
。感想
从头实现一遍
Promise
的 A+ 规范的过程中,对Promise
的一些细枝细节都梳理了一遍,一些之前根本没有注意到的地方也给暴露出来了,特别是 x 如果是一个有then
方法的对象,那么 x 会被包装成一个Promise
,这个地方也是之前没有接触到的。这个过程我用
TypeScript
实现了一遍,具体代码点这里,其中也包括了我写的测试用例,如果你喜欢,欢迎 star。如果你发现我实现过程有不对的地方,欢迎与我探讨。
The text was updated successfully, but these errors were encountered: