
遵循Promises/A+规范,实现Promise原生编写(上)
春节不停更,此文正在参加「星光计划-春节更帖活动」
前言
目前前端开发中异步编程越来越重要,我们不止需要会使用 promise
,更需要懂 promise
原理。
因此本文章将沿着 Promises/A+
规范的思路,一步一步的原生封装 promise
。
基础铺垫
promise
是异步编程的一种解决方案,广泛用在日常编程中。目前我们使用的 Promise
是基于 Promise A+
规范实现的,因此本文的主旋律也是沿着 promise A+
规范实现。
promise
必定处于下列三种状态之一:
Pending
等待态:promise
的初始状态Fulfilled
完成态: 代表promise
已经完成Rejected
失败态: 代表promise
已经失败- 当
promise
处于 Pending 状态时,可以转变 Fulfilled 或者 Rejected当
promise
处于Fulfilled
或Rejected
时,状态不能再发生改变
promise
中什么触发了状态的改变那?我们来看几个栗子:
我们来看一下输出结果:
从输出结果我们可以发现:
- 创建
promise
对象时,需传入一个执行函数(否则会报错,详见p6
),执行函数会立即执行 promise
的初始状态为pending
(见p1
)- 执行
resolve()
和reject()
可以将promise
的状态修改为fulfilled
和rejected
(见p2,p3
) - 若
promise
中抛出错误,相当于执行reject
(见p4
) promise
状态转变只能由pending
开始(见p5
)
学完这些,我们就可以来写第一版代码了。
实现基础 promise
—— 第一版
promise
构造函数实现
- 定义
promise
的三种状态
- 定义
Promise
构造函数,添加必备属性
Promises/A+
规范中指出:
value
是任意的JavaScript
合法值(包括undefined
)reason
是用来表示promise
为什么被拒绝的原因
因此我们使用 ES6 class
定义 Promise
类,value/reason
分别赋值为 undefined
,状态 status
初始为 PENDING
- 定义
promise
时需要传入执行器executor
executor
有两个参数,分别为resolve,reject
executor
会立即执行
- 实现
resolve
和reject
的功能resolve
函数可以将promise
由pending
转变为fulfilled
,并且更新promise
的value
值reject
函数可以将promise
由pending
转变为rejected
,并且更新promise
的reason
值注意:
promise
状态只能由Pending -> Fulfilled
和Pending -> Rejected
promise A+
规范规定,在有异常错误时,则执行失败函数。因此我们要捕获一下executor
的执行。
实现 then
方法的基本功能
then
方法的注意事项比较多,咱们一起阅读规范一起举例说明一下。
promise.then
接受两个参数:
onFulfilled
和onRejected
是可选参数,两者如果不是函数,则会**“忽略”**掉(真的是简单的忽略掉吗?请看下文分解)- 如果
onFulfilled
是一个函数,当promise
状态为Fulfilled
时,调用onFulfilled
函数,onRejected
类似,当promise
状态为Rejeted
时调用。
我们继续来看几个案例:
我们来看一下输出结果:
通过上面的输出结果,我们可以初步分析出 then
的调用逻辑
- 执行
resolve
后,promise
状态改变为fulfilled
,onFulfilled
函数调用,参数值为value
。 - 执行
reject
或 抛出错误,promise
状态改变为rejected
,onRejected
函数调用,参数值为reason
。
跟着上面的步骤,我们来模拟实现一下 then
代码写到这里,第一版 promise
的代码就实现完成了。我们使用上面案例来测试一下,目前手写 promise
是否能实现基本功能。
输出结果如下:
跟原生 promise
的输出结果是相同的,因此,第一版 promise
实现是成功的。
promise
执行多次 then
方法
我们继续往下读规范:
如果一个 promise
调用多次 then
: 当 promise
状态为 Fulfilled
时,所有的 onFulfilled
函数按照注册顺序调用。当 promise
状态为 Rejected
时,所有的 onRejected
函数按照注册顺序调用。
这个规范讲的是什么意思那?小包来举个例子
输出结果:
通过上面的案例,将该条规范总结:同一个 promise
可以注册多个 then
方法,then
方法回调的执行按照注册顺序。
我们测试一下第一版手写 Promise
,输出结果为
第一版代码是可以满足当前规范的,~~~,放松一下,我们来继续实现。
处理异步逻辑——第二版
文章刚开始我们就讲过,promise
是异步编程的一种解决方案,那我们来测试一下第一版 Promise
是否可以实现异步。
没有任何输出,失败了,没事,我们来分析一下为何没有任何输出。
如果 Promise
内部存在异步调用,当执行到 then
函数时,此时由于 resolve/reject
处于异步回调之中,promise
的状态仍为 Pending
,因此 onFulfilled
和 onRejected
都无法执行。
那我们应该如何实现异步调用那?我们来分析一下:
- 如果
promise
还处于pending
状态时,就调用了then
方法,说明是存在异步的。 - 由于此时无法调用
onFulfilled/onRejected
,将onFulfilled/onRejected
存储起来 - 当异步回调中执行
resolve
或者reject
时,依次调用相应的onFulfilled/onRejected
代码实现
- 在
promise
中定义两个数组onFulfilledCallbacks
和onRejectedCallbacks
,分别用来存储onFulfilled
和onRejected
函数
then
方法执行时,若promise
处于Pending
状态,将onFulfilled
和onRejected
函数分别压入onFulfilledCallbacks
和onRejectedCallbacks
——形成订阅
- 当调用
resolve/reject
时,分别执行onFulfilledCallbacks
和onRejectedCallbacks
数组中的函数
我们来整理一下代码,形成手写 Promise 第二版
使用刚才的案例进行测试,输出结果为
第二版 Promise
已经可以成功支持异步逻辑了,撒花~~~ 休息一会,我们继续进行第三版的封装。
链式调用
我们继续去读规范:
then
方法必须返回一个promise
- 如果 onFulfilled 或 onRejected 返回值 x ,则运行
Promise Resolution Procedure [[Resolve]](promise2, x)
(这里暂且将他理解为 resolve(x))
- 如果
onFulfilled
或onRejected
抛出异常e
,则promise2
调用reject(e)
- 如果
onFulfilled
不是函数,且promise
状态为Fulfilled
,那么promise2
应该接受同样的值,同时状态为Fulfilled
- 如果
onRejected
不是函数,且promise
状态为Rejected
,那么promise2
应该接受同样的原因,同时状态为Rejected
代码实现
那我们就照着规范一步一步的实现它
then
需要返回一个promise
,递归调用Promise
,在then
中定义一个promise
并返回
- 如果
onFulfilled
或onRejected
返回值x
,则调用返回promise2
的resolve
函数。
由于需要调用 promise2 deresolve
函数,那说明 onFulfilled
和 onRejected
调用发生在 promise2
的 executor
中。
难点在于如何处理 Pending
状态,当 promise
处于 Pending
状态时,我们将 onFulfilled
和 onRejected
函数先存储起来。但是链式调用需要 onFulfilled/onRejected
函数的返回值,因此我们不能简单的将函数存储起来。
我们将函数封装成下面效果,然后存储起来。
因此目前的 then
方法:
- 如果
onFulfilled
或onRejected
抛出异常 e ,则promise2
调用reject(e)
使用 try catch
捕捉函数执行,当出现异常,执行 reject(e)
- 处理
onFulfilled
和onRejected
非函数情况。
通过上面的案例,我们可以发现,当 onFulfilled
不是函数时,then
方法将其的值传递下去;当 onRejected
不是函数时,then
方法同样会传递错误。
我们如何才能连续传递值或者异常那?我们来看一下下面这两个函数,是否能满足要求
promise
原生编写还差另一个核心部分 resolvePromise
,resolvePromise
内部包含很多规范,因此小包准备另开一篇文章详解。
