这是 JS 原生办法原理探索系列的第十篇文章。本文会介绍如何手写一个合乎 Promise A+ 标准的 Promise,并顺带实现 Promise 的相干办法。
实现 Promise/A+
术语
为了更好地浏览本文,先约定一些术语和说法:
- promise 初始的时候状态还没有落定,处于 pending 状态;它能够落定为 resolved 状态(fulfilled 状态),用 value 示意它 resolve 的值;也能够落定为 rejected 状态,用 reason(拒因)示意它 reject 的值。
- then 办法承受的胜利回调函数称为 onFulfilled,失败回调函数称为 onRejected
实现 Promise 构造函数
咱们先尝试实现一个根底的 Promise 构造函数。
首先,用三个常量示意 promise 实例的状态:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
Promise 构造函数的作用是创立一个 promise 实例。对于一个 promise 实例来说,它会有几个根本的属性:status 记录 promise 的状态(初始为 pending),value 记录 promise resolve 的值(初始为 null),reason 记录 promise reject 的值(初始为 null)。
咱们别离在 Promise 构造函数中进行定义:
function Promise(){
this.status = PENDING
this.value = null
this.reason = null
}
在 new 调用 Promise 构造函数的时候,会往构造函数中传入一个执行器函数 executor,这个执行器函数会马上执行,并且它自身承受 resovle 函数和 reject 函数作为参数。resolve 函数和 reject 函数负责理论扭转 promise 的状态,它们的调用机会取决于开发者本人定义的执行器函数的逻辑,咱们只须要编写调用执行器函数的代码即可。
所以代码进一步拓展如下:
function Promise(executor){
// 保留 promise 实例的援用,不便在 resolve 函数和 reject 函数中拜访
let self = this
self.status = PENDING
self.value = null
self.reason = null
// 定义 resolve 函数
function resolve(){ ...}
// 定义 reject 函数
function reject(){ ...}
// 调用执行器函数
executor(resolve,reject)
}
resolve 函数和 reject 函数的作用是别离承受 value 和 reason 作为参数,并基于这两个值扭转 promise 的状态,但为了确保 promise 状态的不可逆,必须在确定 promise 状态为 pending 的时候,能力批改其状态。所以 resolve 函数和 reject 函数定义如下:
function resolve(value){if(self.status === PENDING){
self.status = FULFILLED
self.value = value
}
}
function reject(reason){if(self.status === PENDING){
self.status = REJECTED
self.reason = reason
}
}
开发者会给 Promise 构造函数传入一个自定义的 executor,executor 中可能会调用 resolve 或者 reject,从而最终创立一个状态落定的 promise 实例。但依据标准的说法,executor 自身执行的时候可能是会抛出异样的,如果是这样,须要捕捉异样并返回一个 reject 该异样的 promise 实例。所以批改代码如下:
function Promise(executor){
let self = this
self.status = PENDING
self.value = null
self.reason = reason
function resolve(value){if(self.status === PENDING){
self.status = FULFILLED
self.value = value
}
}
function reject(reason){if(self.status === PENDING){
self.status = REJECTED
self.reason = reason
}
}
// 捕捉调用 executor 的时候可能呈现的异样
try{executor(resolve,reject)
} catch(e) {reject(e)
}
}
实现 promise 实例的 then 办法
1)初步实现 then 办法
所有的 promise 实例都能够调用 then 办法。then 办法负责对状态落定的 promise 作进一步的解决,它承受胜利回调函数 onFulfilled 和失败回调函数 onRejected 作为参数,而 onFulfilled 和 onRejected 又别离承受 promise 的 value 和 reason 作为参数。
then 办法始终是同步执行的,依据执行 then 办法的时候 promise 状态的不同,会有不同的解决逻辑:
(1)如果 promise 是 resolved 状态,则执行 onFulfilled 函数
(2)如果 promise 是 rejected 状态,则执行 onRejected 函数
(3)如果 promise 是 pending 状态,则临时不会执行 onFulfilled 函数和 onRejected 函数,而是先将这两个函数别离放到一个缓存数组中,等到未来 promise 状态落定的时候,再从数组中取出对应的回调函数执行
(留神:实际上,onFulfilled 和 onRejected 的执行是异步的,但目前咱们临时认为它们是同步执行)
无论是以上哪一种状况,调用 then 办法之后最终都会返回一个新的 promise 实例,这是 then 办法能够实现链式调用的一个要害。
依照下面的说法,初步实现的 then 办法如下:
Promise.prototype.then = function (onFulfilled,onRejected) {
// 因为是 promise 实例调用 then 办法,所以 this 指向实例,这里保留以备后用
let self = this
// 最终返回的 promise
let promise2
// 1)如果是 fulfilled 状态
if(self.status === FULFILLED){return promise2 = new Promise((resolve,reject) => {onFulfilled(self.value)
})
}
// 2)如果是 rejected 状态
else if(self.status === REJECTED){return promise2 = new Promise((resolve,reject) => {onRejected(self.reason)
})
}
// 3)如果是 pending 状态
else if(self.status === PENDING){return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {onFulfilled(self.value)
})
self.onRejectedCallbacks.push(() => {onRejected(self.reason)
})
})
}
}
2)批改 Promise 构造函数:新增两个缓存数组
能够看到,这里多了两个缓存数组:onFulfilledCallbacks
和 onRejectedCallbacks
,它们实际上也是挂载到 promise 实例上的,因而批改一下 Promise 构造函数:
function Promise(executor){
// ... 省略其它代码...
// 新增两个缓存数组
self.onFulfilledCallbacks = []
self.onRejectedCallbacks = []}
3)批改 resolve 和 reject 函数:执行缓存数组中的回调函数
因为执行 then 办法的时候,后面 promise 的状态还没有落定,咱们并不知道应该执行哪个回调函数,因而抉择把胜利回调和失败回调先存入缓存数组中。那么什么时候应该执行回调函数呢?必然是 promise 状态落定的时候,又因为 promise 状态的落定依附的是 resolve 函数和 reject 函数,因而这两个函数执行的机会,正是缓存数组中的回调函数执行的机会。
批改一下 resolve 函数和 reject 函数:
function resolve(value){if(self.status === PENDING){
self.status = FULFILLED
self.value = value
// 遍历缓存数组,取出所有胜利回调函数执行
self.onFulfilledCallbacks.forEach(fn => fn())
}
}
function reject(reason){if(self.status === PENDING){
self.status = REJECTED
self.reason = reason
// 遍历缓存数组,取出所有胜利回调函数执行
self.onRejectedCallbacks.forEach(fn => fn())
}
}
4)改良 then 办法:异样捕捉
依据标准的说法,在执行胜利回调或者失败回调的时候,回调自身可能抛出异样,如果是这样,则须要捕捉该异样,并且最终返回一个 reject 该异样的 promise 实例。
因而在所有执行回调的中央包裹上 try...catch
,改良 then 办法如下:
Promise.prototype.then = function (onFulfilled,onRejected) {
// 因为是 promise 实例调用 then 办法,所以 this 指向实例,这里保留以备后用
let self = this
// 最终返回的 promise
let promise2
if(self.status === FULFILLED){return promise2 = new Promise((resolve,reject) => {
try {onFulfilled(self.value)
} catch (e) {reject(e)
}
})
}
else if(self.status === REJECTED){return promise2 = new Promise((resolve,reject) => {
try {onRejected(self.reason)
} catch (e) {reject(e)
}
})
}
else if(self.status === PENDING){return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {
try {onFulfilled(self.value)
} catch (e) {reject(e)
}
})
self.onRejectedCallbacks.push(() => {
try {onRejected(self.reason)
} catch (e) {reject(e)
}
})
})
}
}
5)改良 then 办法:实现值的穿透
有时候,可能不会给 then 办法传函数类型的参数,或者基本没有传参数,比方:
// 传入非函数类型的参数
Promise.reject(1).then(null,{}).then(null,err => {console.log(err) // 仍然失常打印 1
})
// 没有传参数
Promise.resolve(1).then().then(res => {console.log(res) // 仍然失常打印 1
})
但即便如此,初始 promise 的 value 或者 reason 仍然能够穿透 then 办法,往下传递,这就是 promise 值穿透的个性。要实现这个个性,实际上能够先判断传给 then 办法的参数是不是函数,如果不是(蕴含没有传参的状况),那么就自定义一个回调函数:
- onFulfilled 如果不是函数:定义一个返回 value 的函数,将 value 往下传递,由前面的胜利回调捕捉
- onRejected 如果不是函数:定义一个抛出 reason 的函数,将 reason 往下传递,由前面的失败回调捕捉
因而改良 then 办法如下:
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : value => value
onRejected = typeof onRejected === 'function' ?
onRejected : reason => {throw reason}
// ... 省略其它代码...
}
6)改良 then 办法:确定 then 办法的返回值
目前为止,咱们还没有实现最要害的逻辑,也就是确定 then 办法的返回值 —— 尽管后面的代码曾经让 then 办法返回了一个 promise,然而咱们并没有确定这个 promise 的状态。
调用 then 之后返回的 promise 的状态,取决于回调函数的返回值,这部分的逻辑比较复杂,咱们会用一个 resolvePromise 函数独自进行解决,而 then 办法外部只负责调用这个办法。
改良 then 办法如下:
Promise.prototype.then = function (onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : value => value
onRejected = typeof onRejected === 'function' ?
onRejected : reason => {throw reason}
let self = this
// 最终返回的 promise
let promise2
if(self.status === FULFILLED){return promise2 = new Promise((resolve,reject) => {
try {let x = onFulfilled(self.value)
// 用 resolvePromise 解决 then 的返回值
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
}
else if(self.status === REJECTED){return promise2 = new Promise((resolve,reject) => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
}
else if(self.status === PENDING){return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {
try {let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
self.onRejectedCallbacks.push(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
}
实现 resolvePromise 办法
始终要记住 resolvePromise 的指标是 基于回调函数返回值确定调用 then 之后返回的 promise 的状态,因而 resolvePromise 中的 resolve 调用或者 reject 调用将会决定最终返回的 promise 的状态
1)大抵思路
实现 resolvePromise 办法的大抵思路如下:
-
首先判断回调函数的返回值 x 是否等于调用 then 之后的返回值 promise2,如果相等,则间接返回一个 reject,拒因(reason)是一个 TypeError。这是因为,promise2 的状态取决于 x,如果两者是同一个对象,阐明它须要本人决定本人的状态,这是做不到的。
// 这样是会报错的,因为 then 的返回值等于回调函数的返回值 let p = Promise.resolve(1).then(res => p)
-
接着判断 x 是不是一个非 null 对象或者函数:
- 如果不是:则 x 绝不可能是一个 thenable,此时间接 resolve x 即可
-
如果是,再判断
x.then
是不是一个函数:- 如果是:则 x 是一个 thenable,往后持续解决
- 如果不是:则 x 是一个非 thenable 的对象或函数,间接 resolve x 即可
依照这个思路实现的代码如下:
function resolvePromise(promise2,x,resolve,reject){
// 如果 promise2 和 x 是同一个对象,则会导致死循环
if(promise2 === x){return reject(new TypeError('Chaining cycle'))
}
// 如果 x 是对象或者函数
if(x !== null && typeof x === 'object' || typeof x === 'function'){
// 如果 x 是一个 thenable
if(typeof x.then === 'function'){//... 持续解决...}
// 否则
else {resolve(x)
}
}
// 否则
else {resolve(x)
}
}
2)如何解决 x 是 thenable 的状况
如果 x 是一个 thenable(包含 x 是 promise 的状况),应该怎么解决呢?先看一个例子:
let p1 = Promise.resolve(1).then(res => {return new Promise((resolve,reject) => {resolve(2)
})
})
let p2 = Promise.resolve(1).then(res => {return new Promise((resolve,reject) => {reject(2)
})
})
// 打印 p1 和 p2 的后果,别离是:Promise <fulfilled> 2
Promise <rejected> 2
能够看到,回调函数的返回值 x 是一个 thenable 的时候,调用 then 之后返回的 promise 会沿用 x 的 value 或者 reason。
因而咱们要做的事件其实很简略,那就是在判断 x 是一个 thenable 之后,马上调用它的 then 办法,并且传入 resolve 和 reject 作为胜利回调和失败回调。不论 x 的状态是否落定,它总会在某一个时刻基于本人的状态去调用 resolve 或者 reject,而且也会传入 x 的 value 或者 reason,这样就相当于咱们调用了 resolve(value)
或者 reject(reason)
,因而得以确定调用 then 之后返回的 promise 的状态。
然而这里有一个问题,思考上面的代码:
let p1 = Promise.resolve(1).then(res => {return new Promise((resolve,reject) => {resolve(new Promise((resolve,reject) => {resolve(2)
}))
})
})
let p2 = Promise.resolve(1).then(res => {return new Promise((resolve,reject) => {reject(new Promise((resolve,reject) => {resolve(2)
}))
})
})
// 打印 p1 和 p2 的后果,别离是:Promise {<fulfilled> : 2}
Promise {<rejected> : Promise}
这里的区别在于,尽管回调函数也是返回一个 promise,然而这个 promise 外部 resolve 的还是一个 promise。如果依照先前的说法间接调用 resolve(value)
,则最终返回的 promise 是一个 resolve promise 的 promise,但实际上,它应该是一个 resolve 最里层 value(本例是 2)的 promise。所以,这里不能间接应用 resolve(value)
,而应该递归调用 resolvePromise(promise2,value,resolve,reject)
,直到找到最里层的根底值作为最终 resolve 的值。
不过,如果回调函数返回的 promise 外部 reject 的还是一个 promise,则最终返回的 promise 也是一个 reject promise 的 promise,这种状况并不需要递归调用找到最里层的根底值。
因而,这部分的代码如下:
function resolvePromise(promise2,x,resolve,reject){if(promise2 === x){return reject(new TypeError('Chaining cycle'))
}
if(x !== null && typeof x === 'object' || typeof x === 'function'){if(typeof x.then === 'function'){
x.then((y) => {resolvePromise(promise2,y,resolve,reject)
},
(r) => {reject(r)
})
} else {resolve(x)
}
} else {resolve(x)
}
}
3)其它须要留神的要点
参照标准能够发现,咱们的 resolvePromise 函数还有不少须要改良的中央:
1)Promise 有很多不同版本的实现,它们的具体行为可能会有差别。为了保障不同版本的 Promise 实现能够互操作,进步兼容性,在 resolvePromise 办法中会解决一些比拟非凡的状况。包含:
- x 的 then 属性可能通过
Object.defineProperty
定义了一个 getter,并且每次 get 的时候会抛出异样。因而一开始须要先尝试获取x.then
,并捕捉可能呈现的异样 —— 一旦捕捉到,就 reject 该异样(这代表最终返回的是一个 reject 该异样的 promise) - 在调用 then 的时候,不会通过
x.then
调用,而是通过then.call(x)
调用。这是为什么呢?首先,咱们曾经在后面通过let then = x.then
拿到 then 办法的援用了,所以这里思考的是不要再反复去获取,而是间接应用then
变量,但间接应用又会导致它失落 this 指向,所以须要用call
绑定 this 为 x。
2)传给 then 的胜利回调和失败回调可能会执行屡次,如果是这样,应该以最先执行的回调为准,其它的执行会被疏忽。因而会用变量 called
标记某个回调是否已被执行
3)调用 then 的时候可能也会抛出异样,如果是这样,也要 reject 这个异样。但如果捕捉异样的时候曾经调用了胜利回调或者失败回调,则不须要再 reject 了。
依据下面提到的要点进行改良,最初的 resolvePromise 函数如下:
function resolvePromise (promise2,x,resolve,reject) {if(promise2 === x){return reject(new TypeError('Chaining cycle'))
}
if(x !== null && typeof x === 'object' || typeof x === 'function'){
let then
let called = false
try {then = x.then} catch (e) {return reject(e)
}
if (typeof then === 'function') {
try {then.call(x,(y) => {if(called) return
called = true
resolvePromise(promise2,y,resolve,reject)
},(r) => {if(called) return
called = true
reject(r)
})
} catch (e) {if(called) return
reject(e)
}
} else {resolve(x)
}
} else {resolve(x)
}
}
实现回调函数的异步执行
最初,还须要留神的是 then 的回调函数的执行机会。
如果只看后面代码的实现,会认为在 promise 状态落定的状况下,执行 then 就会同步执行外面的回调,但实际上并非如此 —— then 外面的回调是异步执行的。这点标准也有提到:
In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously.
具体地说,执行 then 的时候:
- 如果后面的 promise 状态落定:那么会先把 then 的回调推入工作队列,等同步代码执行结束再从队列中取出回调执行。
- 如果后面的 promise 状态未落定:那么会先把 then 的回调存入对应的缓存数组中,等 promise 的状态落定后,再从对应的数组中取出回调,推入工作队列中,等同步代码执行结束再从队列中取出回调执行。
那么问题来了,回调函数的执行是属于微工作还是宏工作呢?
能够看一下标准的说法:
This can be implemented with either a“macro-task”mechanism such as
setTimeout
orsetImmediate
, or with a“micro-task”mechanism such asMutationObserver
orprocess.nextTick
.
说得很分明了,A+ 标准只是明确了回调函数必须是异步执行的,并没有要求它必须是微工作或者宏工作。也就是说,依赖宏工作去实现 Promise 也是没有问题的,实践上也能够通过 A+ 测试。真正要求 Promise 必须依赖微工作去实现的是 HTML 规范,这在相干的文档中也能够查失去。
所以,如果要模仿回调函数的异步执行,也有两种形式。第一种就是基于宏工作去实现,用 setTimeout
包裹回调函数的执行;第二种则是基于微工作去实现,能够思考应用 queueMicrotask
或者 process.nextTick
。
1)基于宏工作的实现
回调函数的执行逻辑是在 then 办法中编写的,因而只须要批改 then 办法,在原先执行回调函数的逻辑里面包裹上一个 setTimeout
即可:
Promise.prototype.then = function (onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : value => value
onRejected = typeof onRejected === 'function' ?
onRejected : reason => {throw reason}
let self = this
// 最终返回的 promise
let promise2
if(self.status === FULFILLED){return promise2 = new Promise((resolve,reject) => {setTimeout(() => {
try {let x = onFulfilled(self.value)
// 用 resolvePromise 解决 then 的返回值
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if(self.status === REJECTED){return promise2 = new Promise((resolve,reject) => {setTimeout(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if(self.status === PENDING){return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {setTimeout(() => {
try {let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
self.onRejectedCallbacks.push(() => {setTimeout(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
})
}
}
这样,调用 setTimeout
的时候,只会把传给它的回调函数放入宏工作队列,能够认为就是把胜利回调或者失败回调放入宏工作队列。
2)基于微工作的实现
同样的,如果想要基于微工作去实现 Promise,能够用 queueMicrotask
去包裹回调函数的执行,这样能够将其执行放到一个微工作队列中。
Promise.prototype.then = function (onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : value => value
onRejected = typeof onRejected === 'function' ?
onRejected : reason => {throw reason}
let self = this
// 最终返回的 promise
let promise2
if(self.status === FULFILLED){return promise2 = new Promise((resolve,reject) => {queueMicrotask(() => {
try {let x = onFulfilled(self.value)
// 用 resolvePromise 解决 then 的返回值
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if(self.status === REJECTED){return promise2 = new Promise((resolve,reject) => {queueMicrotask(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if(self.status === PENDING){return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {queueMicrotask(() => {
try {let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
self.onRejectedCallbacks.push(() => {queueMicrotask(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
})
}
}
在 Node 环境下,还能够应用 process.nextTick()
代替 queueMicrotask
。
PS:另外须要留神的是,Node v11 之后才引入了 queueMicrotask
办法,因而要留神降级 Node 的版本,否则会无奈通过 A+ 测试。
欠缺 resolve 函数
实际上,咱们以后的代码曾经能够通过 A+ 测试了,不过,咱们的 resolve 函数还有须要欠缺的中央。
先看 reject 函数。new Promise 创立实例的时候,如果 reject 函数承受的参数也是一个 promise,那么最终返回的 实例会是怎么样的呢?应用原生 Promise 试一下:
let p1 = new Promise((resolve,reject) => {reject(new Promise((resolve,reject) => {resolve(123)
}))
})
let p2 = new Promise((resolve,reject) => {reject(new Promise((resolve,reject) => {reject(123)
}))
})
// 打印 Promise {fulfilled: 123}
p1.then(null,e => {console.log(e)
})
// 打印 Promise {rejected: 123}
p2.then(null,e => {console.log(e)
})
能够看到,即便 reject 函数承受的参数是一个 promise,它也会以这一整个 promise 作为 reason,返回一个 rejected 状态的 promise。而咱们后面实现的 reject 函数的逻辑也正是这样的,这阐明这个函数的实现没有问题。
但 resolve 函数就不一样了。应用原生 Promise 试一下:
let p1 = new Promise((resolve,reject) => {resolve(new Promise((resolve,reject) => {resolve(123)
}))
})
let p2 = new Promise((resolve,reject) => {resolve(new Promise((resolve,reject) => {reject(123)
}))
})
// 打印 value 123
p1.then((value) => {console.log('value',value)},
(reason) => {console.log('reason',reason)}
)
// 打印 reason 123
p2.then((value) => {console.log('value',value)},
(reason) => {console.log('reason',reason)}
)
能够看到,如果给 resolve 函数传入的是 resolved 状态的 promise(这里嵌套多少层 resolved 状态的 promise 都一样),则最终会返回一个 resolve 最里层 value 的 promise;如果传入的是 rejected 状态的 promise,则最终会返回一个和它“一样的”promise(状态一样,reason 也一样)。
然而依照咱们后面实现的 resolve 函数的逻辑,咱们对立将传给 resolve 的参数作为 value,并始终返回一个 resolved 状态的 promise。很显著,这和原生的行为是不合乎的(留神,没有说这是谬误的,因为 A+ 标准对这一点并没有提出要求)。那么应该怎么批改呢?
其实也很简略,那就是检测传给 resolve 的参数是不是 promise,如果是的话,就通过这个参数持续调用 then 办法。这样,如果参数是 rejected 状态的 promise,则调用 then 意味着调用失败回调函数 reject,并传入参数的 reason,从而确保最终返回的是一个和参数状态雷同、reason 也雷同的 promise;而如果参数是 resolved 状态的 promise,则调用 then 意味着调用胜利回调函数 resolve,并传入参数的 value,从而确保最终返回的是一个和参数状态雷同、value 也雷同的 promise —— 即便存在多个 resolved 状态的 promise 的嵌套也没关系,反正咱们最初总能够拿到最里层 resolve 的值。
所以,批改后的 resolve 函数如下:
function resolve(value){if (value instanceof Promise) {return value.then(resolve,reject)
}
if(self.status === PENDING){
self.status = FULFILLED
self.value = value
self.onFulfilledCallbacks.forEach(fn => fn())
}
}
最终的代码
最终实现的代码如下:
// promise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise (executor) {
let self = this
self.status = PENDING
self.value = null
self.reason = null
self.onFulfilledCallbacks = []
self.onRejectedCallbacks = []
function resolve(value){if (value instanceof Promise) {return value.then(resolve,reject)
}
if(self.status === PENDING){
self.status = FULFILLED
self.value = value
self.onFulfilledCallbacks.forEach(fn => fn())
}
}
function reject(reason){if(self.status === PENDING){
self.status = REJECTED
self.reason = reason
self.onRejectedCallbacks.forEach(fn => fn())
}
}
try {executor(resolve,reject)
} catch (e) {reject(e)
}
}
Promise.prototype.then = function (onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : e => {throw e}
let self = this
let promise2
if (self.status === FULFILLED) {return promise2 = new Promise((resolve,reject) => {queueMicrotask(() => {
try {let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if (self.status === REJECTED) {return promise2 = new Promise((resolve,reject) => {queueMicrotask(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
}
else if (self.status === PENDING) {return promise2 = new Promise((resolve,reject) => {self.onFulfilledCallbacks.push(() => {queueMicrotask(() => {
try {let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
self.onRejectedCallbacks.push(() => {queueMicrotask(() => {
try {let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {reject(e)
}
})
})
})
}
}
function resolvePromise (promise2,x,resolve,reject) {if(promise2 === x){return reject(new TypeError('Chaining cycle!'))
}
if (x !== null && typeof x === 'object' || typeof x === 'function') {
let then
try {then = x.then} catch (e) {reject(x)
}
if (typeof then === 'function') {
let call = false
try {then.call(x,(y) => {if(called) return
called = true
resolvePromise(promise2,y,resolve,reject)
},(r) => {if(called) return
called = true
reject(r)
})
} catch (e) {if(called) return
reject(e)
}
} else {resolve(x)
}
} else {resolve(x)
}
}
Promise A+ 测试
能够借助 promises-aplus-test 这个库对咱们实现的 Promise 进行测试。
先通过 npm 装置:
npm install promises-aplus-test -D
接着在 promise.js
文件中增加:
// promise.js
Promise.defer = Promise.deferred = function () {let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
最初运行测试:
promises-aplus-test ./promise.js
测试后果如下:
胜利通过 872 个测试用例,阐明咱们实现的 Promise 是合乎 A+ 标准的。如果某些测试用例没有通过,能够再对照标准改一改。
实现 Promise 的静态方法和原型办法
Promise A+ 标准并没有对 Promise 的静态方法和原型办法(除了 then 办法)的实现提出要求,然而有了后面的根底,实现这些办法也并不难。上面咱们一个一个来实现。
Promise.resolve()
Promise.resolve
承受的参数如果是一个 promise,则最终将这个 promise 原样返回;如果是一个 thenable,则返回一个采纳该 thenable 状态的 promise;其它状况下,一律返回 resolve 给定参数的 promise。
实现如下:
Promise.resolve = (param) => {if(param instanceof Promise){return param}
return new Promise((resolve,reject) => {
// 如果是 thenable
if(param && param.then && typeof param.then === 'function'){param.then(resolve,reject)
} else {resolve(param)
}
})
}
PS:为什么调用 param.then(resolve,reject)
能够让返回的 promise 沿用 param 的状态呢?因为 param 总会在某一个时刻执行 then 外面的某个回调,并且传入对应的参数 —— 也就是执行 resolve(value)
或者 reject(reason)
,而这个执行是在返回的 promise 外部进行的,所以返回的 promise 肯定会沿用 param 的状态。
Promise.reject()
任何状况下,Promise.reject()
都会返回一个 reject 给定参数的 promise:
Promise.reject = (param) => {return new Promise((resolve,reject) => {reject(param)
})
}
Promise.all()
Promise.all()
承受的参数:
- 不可迭代时,返回一个 rejected 状态的 promise;
- 可迭代时,如果是空的可迭代对象,则返回一个 resolve 空数组的 promise;
-
可迭代时,如果是非空的可迭代对象:
- 不蕴含 rejected 状态和 pending 状态的 promise,则返回一个 resolve 后果数组的 promise,后果数组中蕴含各个 promise 的 resolved 值
- 蕴含一个 rejected 状态的 promise,返回一个雷同的 promise
- 不蕴含 rejected 状态的 promise,但蕴含 pending 状态的 promise,则返回一个 pending 状态的 promise
PS:可迭代对象中的每个成员都会被 Promise.resolve()
包装成一个 promise
因而实现的代码如下:
Promise.all = (promises) => {
// 判断是否可迭代
let isIterable = (params) => typeof params[Symbol.iterator] === 'function'
return new Promise((resolve,reject) => {
// 如果不可迭代
if(!isIterable(promises)) {reject(new TypeError(`${promises} is not iterable!`))
} else {let result = []
let count = 0
if(promises.length === 0){resolve(result)
} else {for(let i = 0;i < promises.length;i++){Promise.resolve(promises[i]).then((value) => {
count++
result[i] = value
if(count === promises.length);resolve(result)
},reject)
}
}
}
})
}
能够看到,咱们会遍历 promises
中的每一个成员,用 count 记录 resolved 状态的 promise 的个数,并把它们的 value 存入后果数组中,只有发现所有成员都是 resolved 状态的 promise,就会返回一个 resolve 后果数组的 promise;而只有发现有一个 rejected 状态的 promise,就会以它的 reason 作为 reason,返回一个 rejected 状态的 promise;如果存在 pending 状态的 promise,则必不可能执行 resolve 或者 reject,因而最初会返回一个同样是 pending 状态的 promise。
Promise.race()
和 Promise.all()
相似,但 Promise.race()
只要求有一个 promise 状态落定即可,并且最终会返回一个和它一样的 promise。如果传入的是一个空的可迭代对象,则意味着它永远无奈失去一个冀望的状态落定的 promise,然而,它还是会持续期待上来,因而最终会返回一个 pending 状态的 promise。
实现代码如下:
Promise.race = (promises) => {let isIterable = (param) => typeof param[Symbol.iterator] === 'function'
return new Promise((resolve,reject) => {if (!isIterable(promises)) {reject(new TypeError(`${promises} is not iterable!`))
} else {for(let i = 0;i < promises.length;i++){Promise.resolve(promises[i]).then(resolve,reject)
}
}
})
}
其实大体上来说只有两种状况:
- 一种是 promises 中至多有一个状态落定的 promise,那么遇到这个 promise 的时候,就会通过它去调用 then,进而调用 resolve 或者 reject,使最终返回的 promise 状态落定。即便尔后又遇到了其它状态落定的 promise 并且执行相应的 resolve 或者 reject,也没有关系,因为 promise 的状态是不可逆的
- 另一种是所有 promise 的状态都未落定,这意味着永远不可能执行 then 外面的回调,也即不可能执行 resolve 或者 reject,因而最终返回的是一个 pending 状态的 promise
Promise.allSettled()
和 Promise.all()
相似,也会返回一个 resolve 后果数组的 promise,然而后果数组中会蕴含各个 promise 的 resolved 值或者 rejected 值,相似于这样:
[{status: "fulfilled", value: 11}
{status: "rejected", reason: 22}
{status: "fulfilled", value: 33}
]
如果 promises 中存在 pending 状态的 promise,则无奈达到真正的“allSettled”(全副落定),最终会返回一个 pending 状态的 promise。
实现代码如下:
Promise.allSettled = (promises) => {let isIterable = param => typeof param[Symbol.iterator] === 'function'
return new Promise((resolve,reject) => {if (!isIterable(promises)) {reject(new TypeError(`${promises} is not iterable!`))
} else {let result = []
let count = 0
if(promises.length === 0) {resolve(result)
} else {for(let i = 0;i < promises.length;i++){Promise.resolve(promises[i]).then(
value => {
count++
result[i] = {
status: 'fulfilled',
value
}
if(count === promises.length) resolve(result)
},
reason => {
count++
result[i] = {
status: 'rejected',
reason
}
if(count === promises.length) resolve(result)
}
)
}
}
}
})
}
Promise.prototype.catch()
如果后面的 promise 落定为 rejected 状态,则会执行 catch 办法,因而 catch 办法能够看作是没有传入胜利回调作为参数的 then 办法:
Promise.prototype.catch = (onRejected) => {return this.then(null,onRejected)
}
Promise.prototype.finally()
finally 办法的特点有两个:
- 不论后面的 promise 是 resolved 状态还是 rejected 状态,传给 finally 的回调函数都能够执行
- finally 最初也会返回一个 promise,这个 promise 个别会沿用调用 finally 的 promise 的状态。除非 finally 的回调函数返回了一个 rejected 状态的 promise
最终的实现如下:
Promise.prototype.finally = (fn) => {
let P = this.constructor
return this.then(value => P.resolve(fn()).then(() => value),
reason => P.resolve(fn()).then(() => { throw reason})
)
}
留神几个要点:
1)这里不间接应用 Promise.resolve()
,是因为如果这样写,那么这个 finally 办法就只能兼容咱们的 Promise 版本了;而通过 promise 实例的 constructor 则始终能够获取该实例对应的 Promise 版本
2)因为调用 finally 后返回的 promise 的状态依赖于调用 finally 的 promise 实例,所以返回一个 this.then(...)
,不便获取 promise 实例的 value 或者 reason
3)在 then 的胜利回调和失败回调中,不仅执行了 fn,而且还将其执行后果用 P.resolve()
包装起来,这次要是为了解决 fn 执行后果可能为 promise 的状况 —— 在这种状况下,它可能会影响到最初返回的 promise 的状态。
通过两个例子来了解这样写的目标。比如说:
Promise.resolve(1).finally(() => {return Promise.resolve(2)})
依照咱们的代码,调用 finally 之后将会返回 Promise.resolve(1).then(...)
,走胜利回调的逻辑,获取的 value 就是 1。而 P.resolve(fn())
将会返回 fn()
,也就是 Promise.resolve(2)
,走胜利回调的逻辑,回调返回 value。最终调用 finally 返回的就恰好是一个 resolve 1 的 promise。
但如果是:
Promise.resolve(1).finally(() => {return Promise.reject(2)})
留神这里回调返回的尽管也是 promise,但它是 rejected 状态的。那么调用 finally 之后将会返回 Promise.resolve(1).then(...)
,走胜利回调的逻辑,获取的 value 就是 1。而 P.resolve(fn())
将会返回 fn()
,也就是 Promise.reject(2)
,走失败回调的逻辑,留神这里咱们没有申明失败回调,所以会采纳默认的失败回调,承受后面 promise reject 掉的 2,并将这个 2 抛出去,所以最终调用 finally 返回的就恰好是一个 reject 2 的 promise。这种状况下,最终的 promise 并没有沿用调用 finally 的 promise 的状态,而是依赖于 finally 回调的执行后果。