# 参考 Promise/A+ 规范和测试用例手写 Promise

# 前言
这可能是手写promise较清晰的文章之一。
由浅至深逐步分析了原生测试用例,以及相关Promise/A+规范。阅读上推荐以疑问章节为切入重点,对比Promise/A+规范与ECMAScript规范的内在区别与联系,确定怎样构建异步任务和创建promise实例。然后开始手写章节,过程中代码与测试可参考 promise-coding (opens new window) 仓库。
也试着回答以下关键问题。
- 什么是广义对象?
- 如何检验
promise类型? promise与thenable两类型有何区别?
# 疑问
如果不太清楚Promise,建议参考《ECMAScript 6 入门》 (opens new window),预习下Promise相关用法知识。
除此之外,对规范也要有大致认识,我们将根据几个疑问来细致阐述。
# 什么是 Promise/A+ 规范?
;Promise有多种社区规范,例如 Promise/A (opens new window)、Promise/B (opens new window)、Promise/D (opens new window) 和 Promises/KISS (opens new window) 等。
;Promise/A+ (opens new window) 在Promise/A之上,规范了术语并拓展了参数行为,省略了一些有问题的部分。
;Promise/A+有很多实现,例如第三方库 q (opens new window)、when (opens new window) 和 bluebird (opens new window) 等。实际上任何Promise通过测试,都被认为是对Promise/A+规范的一种实现。
Promise/A+规范官方测试用例为 promises-aplus-tests (opens new window)
# 原生 Promise 实现了 Promise/A+?
在Promise/A+规范 The ECMAScript Specification (opens new window) 章节中。
The ECMAScript Specification
...
Largely due to the actions of the Promises/A+ community, the Promise global specified by ECMAScript and present in any conforming JavaScript engine is indeed a Promises/A+ implementation!
叙述了JavaScript引擎中的Promise也是对Promise/A+规范的一种实现。
为什么呢?
;Promise/A+规范内容上仅说明了Promise状态和then方法。
;ECMAScript规范不仅规定Promise为构造函数,还添加了静态方法,例如Promise.resolve、Promise.all和Promise.race等,新增了原型方法Promise.prototype.catch和Promise.prototype.finally等。其中Promise.prototype.then相关内容,则是根据Promise/A+规范转化而来。
我们知道,JavaScript就是对ECMAScript规范的一种实现,而ECMAScript规范中Promise.prototype.then相关内容又继承了Promise/A+规范。
那么可以说,JavaScript引擎中的Promise,即原生Promise,就是对Promise/A+规范的一种实现。
# 如何构建异步任务?
;Promise/A+规范规定then方法接收两个参数。
promise.then(onFulfilled, onRejected)
在 2.2.4 (opens new window) 小结中明确了参数必须以异步形式运行。
2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
注解 3.1 (opens new window) 补充可用宏任务setTimeout和setImmediate,或者微任务MutationObserver(浏览器环境)和process.nextTick(node环境)达到异步。
3.1. ...In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a "macro-task" mechanism such as setTimeout or setImmediate, or with a "micro-task" mechanism such as MutationObserver or process.nextTick.
综上所述,Promise/A+规范仅规定了参数以异步形式运行,并未规定是宏任务还是微任务。
注意
V8引擎内部为微任务,考虑一致性推荐 queueMicrotask (opens new window) 创建微任务,兼容性相对较好
# 如何创建 promise?
;Promise/A+规范并未提及如何创建promise。
;ECMAScript6规范发布以来,多数是以构造函数方式创建promise。
new Promise(executor)
实际上在此之前,还流行一种Deferred方式,例如 JQuery.Deferred (opens new window)。
$.Deferred()
我们以定时器为例,对比下两者差异。
// ECMAScript6 Promise
const promise = new Promise(resolve => {
setTimeout(() => {
resolve(1)
}, 1000)
})
promise.then(v => {
console.log(v) // 1
})
// JQuery Deferred
const deferred = $.Deferred()
deferred.promise().then(v => {
console.log(v) // 1
})
setTimeout(() => {
deferred.resolve(1)
}, 1000)
你也注意到了吧,Deferred方式相对更加灵活,可以在任何时机修改状态。而Promise方式自由度减少了很多,不仅代码层级多了一层,而且只能在函数参数中修改状态。
可能你会问,那为什么TC39放弃了Deferred,而决定了Promise构造器方式呢?
;Deferred方式存在一个较严重的缺陷,即在业务流程阶段,不容易捕获异常。
const deferred = $.Deferred()
deferred.promise().catch(v => {
console.log(v)
})
;(function () {
throw new Error() // Uncaught Error
deferred.resolve(1)
})()
如果想让promise捕获异常,业务代码可修改为。
;(function () {
try {
throw new Error()
} catch (error) {
deferred.reject(error)
}
deferred.resolve(1)
})()
而Promise构造器方式则非常容易。
const promise = new Promise(resolve => {
throw new Error()
resolve(1)
})
promise.catch(v => {
console.log(v) // Error
})
两相比较下ECMAScript6确定了以构造器方式创建promise。
个人认为
Deferred更多是一个发布订阅器,而Promise则相对更加强大,是一个异步流程解决方案,ECMAScript6规范将其独立为一个模块是相当合理的
# 手写
;Promise/A+更多地是规范了算法逻辑,并未规定语法层面的实现方式。
我们可以参考原生Promise语法,并结合简单用例,手写以符合Promise/A+规范。
注意
Promise/A+规范相关内容将特别标注
# 实例初始属性
原生创建Promise实例。
new Promise(() => {})
// {
// [[PromiseState]]: 'pending',
// [[PromiseResult]]: undefined,
// }
相关特征包括。
Promise为构造函数- 默认状态为
pending,默认结果为undefined - 三种状态分别为等待态
pending、解决态fulfilled和拒绝态rejected——「Promise/A+ 2.1」 (opens new window)
代码编写如下,其中属性[[PromiseState]]用于保存状态,[[PromiseResult]]用于保存结果。
const PromiseState = '[[PromiseState]]'
const PromiseResult = '[[PromiseResult]]'
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Promise {
[PromiseState] = PENDING;
[PromiseResult] = undefined
}
ES2020规范 proposal-class-fields (opens new window) 允许实例属性定义在类内部的最顶层,相对更加清晰简洁
# executor 执行器
原生Promise传参函数。
new Promise(function executor() {
console.log(1) // 1
})
console.log(2) // 2
new Promise((resolve, reject) => {
resolve(3)
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 3,
// }
new Promise((resolve, reject) => {
reject(4)
})
// {
// [[PromiseState]]: 'rejected',
// [[PromiseResult]]: 4,
// }
相关特征包括。
- 实例创建过程中参数
executor将同步执行 - 执行器
executor包括resolve和reject两个函数参数,resolve执行实例状态修改为解决态,reject执行实例状态修改为拒绝态
以下为优化代码,注意私有方法用箭头函数,可将内部this指向实例对象。
class Promise {
...
#resolve = value => {
this[PromiseState] = FULFILLED
this[PromiseResult] = value
}
#reject = reason => {
this[PromiseState] = REJECTED
this[PromiseResult] = reason
}
constructor(executor) {
executor(this.#resolve, this.#reject)
}
}
ES2020规范 proposal-class-fields (opens new window) 允许实例定义私有属性或方法,仅可在类内部使用,外部无法使用
# 状态不可变性
原生Promise修改状态。
new Promise((resolve, reject) => {
resolve(1)
resolve(2)
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 1,
// }
new Promise((resolve, reject) => {
resolve(3)
reject(4)
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 3,
// }
相关特征包括。
- 处于解决态或者拒绝态,一定不能再修改为任何状态——「Promise/A+ 2.1.2 / 2.1.3」 (opens new window)
- 处于等待态的时候,可能变为解决态或者拒绝态——「Promise/A+ 2.1.1」 (opens new window)
代码优化为。
#resolve = value => {
if (this[PromiseState] === PENDING) {
this[PromiseState] = FULFILLED
this[PromiseResult] = value
}
}
#reject = reason => {
if (this[PromiseState] === PENDING) {
this[PromiseState] = REJECTED
this[PromiseResult] = reason
}
}
# 方法传参
原生Promise上then方法传参。
const p = new Promise((resolve, reject) => {
resolve()
})
p.then(undefined, undefined)
相关特征包括。
promise必须有then方法,且接收两个参数onFulfilled和onRejected——「Promise/A+ 2.2」 (opens new window)onFulfilled和onRejected都是可选参数,若不是函数,必须被忽略——「Promise/A+ 2.2.1」 (opens new window)onFulfilled和onRejected一定被作为普通函数调用——「Promise/A+ 2.2.5」 (opens new window)
代码修改为。
class Promise {
...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
onRejected = typeof onRejected === 'function' ? onRejected : () => {}
}
}
参数为非函数时,为保证可被调用,暂时返回普通函数
# then 方法
原生Promise执行then方法。
const p1 = new Promise((resolve, reject) => {
resolve(1)
})
p1.then(v => {
console.log(v) // 1
})
const p2 = new Promise((resolve, reject) => {
reject(2)
})
p2.then(undefined, v => {
console.log(v) // 2
})
相关特征包括。
- 如果
onFulfilled或onRejected是一个函数,必须在promise被解决或被拒绝后调用,且promise值或原因作为第一个参数——「Promise/A+ 2.2.2 / 2.2.3」 (opens new window)
代码修改为。
then(onFulfilled, onRejected) {
...
if (this[PromiseState] === FULFILLED) {
onFulfilled(this[PromiseResult])
}
if (this[PromiseState] === REJECTED) {
onRejected(this[PromiseResult])
}
}
# 异步 executor
目前代码并未完全符合「Promise/A+ 2.2.2 / 2.2.3」 (opens new window)规范,例如executor为异步情况时,还会存在一些问题。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
p.then(v => {
console.log(v)
})
控制台没有打印任何内容
为什么呢?
实例p在创建完成后,还处在等待态。紧接着执行then方法,then方法内部没有等待态相关逻辑,也就没有任何操作。1s后resolve执行,也仅仅是将p状态修改为解决态。
如何解决呢?
可以在等待态就保存onFulfilled和onRejected函数,在resolve修改状态时再执行。
代码优化为。
class Promise {
...
#onFulfilledCallBack = undefined
#onRejectedCallBack = undefined
#resolve = value => {
if (this[PromiseState] === PENDING) {
...
this.#onFulfilledCallBack?.(this[PromiseResult])
}
}
#reject = reason => {
if (this[PromiseState] === PENDING) {
...
this.#onRejectedCallBack?.(this[PromiseResult])
}
}
...
then(onFulfilled, onRejected) {
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBack = onFulfilled
this.#onRejectedCallBack = onRejected
}
}
}
?.为ES2020规范中 proposal-optional-chaining (opens new window) 可选链操作符
# 多次调用 then
原生Promise多次调用then方法。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
p.then(() => {
console.log(1) // 1
})
p.then(() => {
console.log(2) // 2
})
p.then(() => {
console.log(3) // 3
})
相关特征包括。
then方法函数参数按语法顺序执行- 同一
promise上then方法可能被多次调用——「Promise/A+ 2.2.6」 (opens new window)
代码优化如下,注意为了保证顺序,两数组内函数都是先进先出。
class Promise {
...
#onFulfilledCallBacks = []
#onRejectedCallBacks = []
#resolve = value => {
if (this[PromiseState] === PENDING) {
...
while (this.#onFulfilledCallBacks.length) {
this.#onFulfilledCallBacks.shift()(this[PromiseResult])
}
}
}
#reject = reason => {
if (this[PromiseState] === PENDING) {
...
while (this.#onRejectedCallBacks.length) {
this.#onRejectedCallBacks.shift()(this[PromiseResult])
}
}
}
...
then(onFulfilled, onRejected) {
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(onFulfilled)
this.#onRejectedCallBacks.push(onRejected)
}
}
}
# 返回 promise
原生Promise返回值。
const p = new Promise(() => {})
p.then()
// {
// [[PromiseState]]: 'pending',
// [[PromiseResult]]: undefined,
// }
相关特征包括。
then方法必须返回一个新promise——「Promise/A+ 2.2.7」 (opens new window)
代码暂时修改为。
then(onFulfilled, onRejected) {
...
if (this[PromiseState] === PENDING) {
...
}
const promise = new Promise(() => {})
return promise
}
# 函数参数返回值
原生Promise函数参数onFulfilled返回数值。
const p = new Promise((resolve, reject) => {
resolve()
})
p.then(() => {
return 1
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 1,
// }
相关特征包括。
- 如果
onFulfilled或onRejected返回一个值x,运行promise解决程序——「Promise/A+ 2.2.7.1」 (opens new window) - 如果
x不是一个对象或函数,用x解决promise——「Promise/A+ 2.3.4」 (opens new window)
何为promise解决程序呢?
;「Promise/A+ 2.3」 (opens new window)叙述是一个抽象操作,可表示为[[Resolve]](promise, x)。其中主要根据x类型,决定新promise的状态和结果。
比如x不是一个对象或函数,例如数值,则新promise状态将确定为解决态,结果为x,即用x解决promise。
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: x,
// }
那么如何在onFulfilled或onRejected返回数值x时,又修改新promise状态和结果呢?
then(onFulfilled, onRejected) {
...
if (this[PromiseState] === FULFILLED) {
const x = onFulfilled(this[PromiseResult])
}
...
const promise = new Promise(() => {})
return promise
}
你可能想到了。
then(onFulfilled, onRejected) {
...
const promise = new Promise(() => {})
if (this[PromiseState] === FULFILLED) {
const x = onFulfilled(this[PromiseResult])
promise.#resolve(x)
}
...
return promise
}
可修改状态也符合规范,但个人认为此方式存在一些缺陷。
将实例属性resolve私有化,就是为了限制外部访问。以promise.#resolve访问,而非this.#resolve,已经处于外部访问的范畴了,思路上不是很合理。
还有更好的办法吗?
我们知道在executor执行器上,resolve和reject两个参数也可修改状态。
如果将if语句体迁移至executor内部,有没有可能呢?答案是可以的。
then(onFulfilled, onRejected) {
...
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
const x = onFulfilled(this[PromiseResult])
resolve(x)
}
...
})
return promise
}
if语句体在executor外部时,同步执行。在executor内部时,也是同步执行
相关特征完全实现了吗?并没有。
若executor为异步情况时,还存在一些问题。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
const p2 = p1.then(() => {
return 2
})
setTimeout(() => {
console.log(p2)
// {
// [[PromiseState]]: 'pending',
// [[PromiseResult]]: undefined,
// }
}, 2000)
控制台打印内容与原生不一致
为什么呢?
实例p1处于等待态,执行then方法将onFulfilled保存至数组中。1s后resolve执行,p1状态修改为解决态,紧接着取出运行onFulfilled,p2状态无任何变化。
我们可以在onFulfilled执行时,对返回值x稍加处理。
const promise = new Promise((resolve, reject) => {
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(() => {
const x = onFulfilled(this[PromiseResult])
resolve(x)
})
...
}
})
# 处理函数
为了统一处理不同类型x值,并严格实现规范「Promise/A+ 2.3」 (opens new window)中各子章节。
修改代码并创建resolvePromise函数,参数暂时为x和resolve。
class Promise {
...
then(onFulfilled, onRejected) {
...
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
const x = onFulfilled(this[PromiseResult])
resolvePromise(x, resolve)
}
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(() => {
const x = onFulfilled(this[PromiseResult])
resolvePromise(x, resolve)
})
...
}
})
return promise
}
}
function resolvePromise(x, resolve) {
resolve(x)
}
研读部分子章节。
2.3.1. If promise and x refer to the same object, reject promise with a TypeError as the reason.
2.3.2.2. If/when x is fulfilled, fulfill promise with the same value.2.3.2.3. If/when x is rejected, reject promise with the same reason.
可确认参数promise和x、resolve、reject。
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
}
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(() => {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
})
...
}
})
function resolvePromise(promise, x, resolve, reject) {
resolve(x)
}
# 抛出异常
原生Promise抛出异常。
const p = new Promise((resolve, reject) => {
resolve()
})
p.then(() => {
throw new Error()
}).then(undefined, v => {
console.log(v) // Error
})
相关特征包括。
- 如果
onFulfilled或onRejected抛出一个异常e,新promise为拒绝态且原因为e——「Promise/A+ 2.2.7.2」 (opens new window)
代码优化为。
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
try {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
}
...
})
类似地executor为异步情况时,也存在一些问题。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
p.then(() => {
throw new Error() // Uncaught Error
}).then(undefined, v => {
console.log(v)
})
未捕获到异常
为什么呢?
实例p处于等待态,执行then方法将onFulfilled保存。1s后resolve执行,p状态修改为解决态,紧接着取出onFulfilled,运行内部抛出了异常。
代码优化为。
const promise = new Promise((resolve, reject) => {
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(() => {
try {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
...
}
})
# 异步任务
原生Promise异步。
const p = new Promise((resolve, reject) => {
resolve(1)
})
console.log(2) // 2
p.then(v => {
console.log(v) // 1
})
console.log(3) // 3
注意打印顺序
2 3 1
相关特征包括。
onFulfilled或onRejected必须以异步形式运行——「Promise/A+ 2.2.4」 (opens new window)
代码简单修改为。
const queueTask = queueMicrotask
class Promise {
...
then() {
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
try {
queueTask(() => {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
})
} catch (e) {
reject(e)
}
}
...
})
return promise
}
}
注意try...catch并不能捕获到异步函数内抛出的异常,例如。
try {
setTimeout(() => {
throw new Error() // Uncaught Error
})
} catch (error) {
console.log(error)
}
那如何优化呢?
我们可以将全部try...catch语句放到异步函数中。
const promise = new Promise((resolve, reject) => {
if (this[PromiseState] === FULFILLED) {
queueTask(() => {
try {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
...
})
类似地executor为异步情况时,也存在一些问题。
const p = new Promise(resolve => {
setTimeout(() => {
console.log(1) // 1
resolve(2)
console.log(3) // 3
}, 1000)
})
p.then(v => {
console.log(v) // 2
})
打印顺序
1 2 3(原生打印顺序1 3 2)
为什么呢?
;onFulfilled没有以异步形式运行。
代码修改为。
const promise = new Promise((resolve, reject) => {
...
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(() => {
queueTask(() => {
try {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
...
}
})
合并重复代码。
const promise = new Promise((resolve, reject) => {
const resolved = () => {
queueTask(() => {
try {
const x = onFulfilled(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
const rejected = () => {
queueTask(() => {
try {
const x = onRejected(this[PromiseResult])
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this[PromiseState] === FULFILLED) {
resolved()
}
if (this[PromiseState] === REJECTED) {
rejected()
}
if (this[PromiseState] === PENDING) {
this.#onFulfilledCallBacks.push(resolved)
this.#onRejectedCallBacks.push(rejected)
}
})
# 参数优化
原生Promise值穿透。
const p1 = new Promise((resolve, reject) => {
resolve(1)
})
p1.then(undefined)
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 1,
// }
const p2 = new Promise((resolve, reject) => {
reject(2)
})
p2.then(undefined, undefined)
// {
// [[PromiseState]]: 'rejected',
// [[PromiseResult]]: 2,
// }
相关特征包括。
- 如果
onFulfilled不是一个函数且原promise被解决,新promise必须也被解决,且值与原promise相同——「Promise/A+ 2.2.7.3」 (opens new window) - 如果
onRejected不是一个函数且原promise被拒绝,新promise必须也被拒绝,且原因与原promise相同——「Promise/A+ 2.2.7.4」 (opens new window)
代码优化如下。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
...
}
注意
throw抛出异常将被try...catch捕获,进而拒绝新promise
# 类型
如何处理不同类型x呢?
还是参考规范「Promise/A+ 2.3」 (opens new window)各子章节,以优化resolvePromise处理函数。
# 循环引用
原生Promise循环引用。
const p1 = new Promise((resolve, reject) => {
resolve()
})
const p2 = p1.then(() => {
return p2
})
// {
// [[PromiseState]]: 'rejected',
// [[PromiseResult]]: TypeError: Chaining cycle detected for promise #<Promise>,
// }
相关特征包括。
- 如果
promise和x引用同一对象,则拒绝promise,原因为一个TypeError——「Promise/A+ 2.3.1」 (opens new window)
代码优化为。
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
resolve(x)
}
# 传递性
原生Promise函数参数onFulfilled返回promise。
const p1 = new Promise((resolve, reject) => {
resolve()
})
const p2 = new Promise((resolve, reject) => {
reject(1)
})
p1.then(() => {
return p2
})
// {
// [[PromiseState]]: 'rejected',
// [[PromiseResult]]: 1,
// }
相关特征包括。
- 如果
x是等待态,promise必须保持等待态,直到x被解决或被拒绝——「Promise/A+ 2.3.2.1」 (opens new window) - 如果
x是解决态,用相同的值解决promise——「Promise/A+ 2.3.2.2」 (opens new window) - 如果
x是拒绝态,用相同的原因拒绝promise——「Promise/A+ 2.3.2.3」 (opens new window)
也就是promise状态与x始终都保持一致。
可能会存在x初始为等待态,然后又转变为解决态或拒绝态。过程中两者状态始终一致,若x状态转变,promise状态也将转变。
那如何知道x状态转变呢?答案就是then方法。
x.then(onFulfilled, onRejected)
;x转变为解决态时将运行onFulfilled,转变为拒绝态时将运行onRejected。
那我们就可在onFulfilled或onRejected内部去修改promise状态。
代码优化为。
function resolvePromise(promise, x, resolve, reject) {
...
if (x instanceof Promise) {
x.then(value => {
resolve(value)
}, reason => {
reject(reason)
})
} else {
resolve(x)
}
}
简化为。
function resolvePromise(promise, x, resolve, reject) {
...
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
# 广义对象
何为广义对象呢?
能添加属性或方法的变量,都称之为广义上的对象,例如数组、函数等。
创建isObject工具函数,更多参考 lodash.isObject (opens new window)。
function isObject(value) {
const type = typeof value
return value !== null && (type === 'object' || type === 'function')
}
然后阅读规范「Promise/A+ 2.3.3」 (opens new window)小节,省略部分暂时不考虑。
- 如果
x是一个对象或函数(广义对象)- 让
then为x.then - 如果获取
x.then导致抛出了一个异常e,用e作为原因拒绝promise - 如果
then是一个函数,用x作为this调用它,且包含两个参数,分别为resolvePromise和rejectPromise- 如果
resolvePromise用一个值y调用,运行[[Resolve]](promise, y) - 如果
rejectPromise用一个原因r调用,用r拒绝promise ...- 如果调用
then抛出了一个异常e...- 否则用
e作为作为原因拒绝promise
- 如果
- 如果
then不是一个函数,用x解决promise
- 让
转译为代码。
function resolvePromise(promise, x, resolve, reject) {
...
if (x instanceof Promise) {
...
} else {
if (isObject(x)) {
var then
try {
then = x.then
} catch (e) {
reject(e)
}
if (typeof then === 'function') {
try {
then.call(
x,
y => {
resolvePromise(promise, y, resolve, reject)
},
r => {
reject(r)
}
)
} catch (e) {
reject(e)
}
} else {
resolve(x)
}
} else {
resolve(x)
}
}
}
规范中运行[[Resolve]](promise, y),即递归resolvePromise,为什么呢?
原因在于y值可能还是promise或者广义对象等等。
我们来看一个原生Promise示例。
const p = new Promise(resolve => {
resolve()
})
const thenable1 = {
then(reslove) {
reslove(1)
},
}
const thenable2 = {
then(resolve) {
resolve(thenable1)
},
}
p.then(() => {
return thenable2
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 1,
// }
# 优先级
以下为刚才省略的部分。
- 如果
then是一个函数.........- 如果
resolvePromise和rejectPromise都被调用,或者对其中一个多次调用,那么第一次调用优先,以后的调用都会被忽略 - 如果调用
then抛出了...- 如果
resolvePromise或rejectPromise已经被调用,则忽略它 ...
- 如果
为了限制哪种情况呢?
还是来看一个原生Promise示例。
const p = new Promise(resolve => {
resolve()
})
const thenable1 = {
then(reslove) {
setTimeout(() => {
reslove(2)
}, 0)
},
}
const thenable2 = {
then(resolve) {
resolve(thenable1)
resolve(1)
},
}
p.then(() => {
return thenable2
})
// {
// [[PromiseState]]: 'fulfilled',
// [[PromiseResult]]: 2,
// }
代码如何优化呢?
我们可定义布尔变量called,标记是否运行参数resolvePromise或rejectPromise。然后在第一次运行时将called修改为true,而以后的都会return被忽略。
if (typeof then === 'function') {
var called = false
try {
then.call(
x,
y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} catch (e) {
if (called) return
called = true
reject(e)
}
}
# thenable
规范「Promise/A+ 1.1」 (opens new window)小结陈述了。
;promise是一个对象或函数(广义对象),存在then方法且行为符合规范。
第三方Promise库、原生Promise以及我们手写版本Promise,创建的promise实例,其实都是标准的promise类型。
而代码中x instanceof Promise语句,检验是否为promise类型,就有问题了。例如x被第三方库创建,也是标准promise类型,但是并不会运行if语句体,而是错误地运行else语句体。
function resolvePromise(promise, x, resolve, reject) {
...
if (x instanceof Promise) {
...
} else {
...
}
}
还有方法可确定x为promise类型吗?答案是没有。
怎么办呢?
既然无法检验promise类型,那就退而求其次,检验类似promise类型的,即鸭式辩型。
鸭子类型(
duck typing),也叫鸭式辩型,一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
规范「Promise/A+ 1.2」 (opens new window)提出了thenable类型,即定义了then方法的对象或函数。
{
then() {
...
},
}
thenable是promise的鸭子类型
检验是否为promise类型,则降级为检验是否为thenable类型。
代码修改为。
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (isObject(x)) {
var then
try {
then = x.then
} catch (e) {
reject(e)
}
if (typeof then === 'function') {
var called = false
try {
then.call(
x,
y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
} else {
resolve(x)
}
}
# 测试
安装官方测试用例 promises-aplus-tests (opens new window)。
npm i promises-aplus-tests -D
;promise代码中新增以下。
// promise.js
class Promise {
...
}
Promise.deferred = () => {
const result = {}
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = Promise
新增测试命令。
// package.json
{
...
"scripts": {
"test": "promises-aplus-tests promise.js"
},
...
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
运行npm run test。

# 小结
全文共计两万五千字有余,参考Promise/A+规范手写了then方法和promise解决程序。
相关代码可参考 promise-coding (opens new window) 仓库,支持node和浏览器环境测试。
如何手写Promise到此就结束了。
# 扩展
学有余力或意犹未尽的伙伴们。
贴出两个代码片段,可在原生Promise与手写Promise环境下运行。
// 1
new Promise(resolve => {
resolve(Promise.resolve())
}).then(() => {
console.log(3)
})
Promise.resolve()
.then(() => {
console.log(1)
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(4)
})
// 2
Promise.resolve()
.then(() => {
console.log(0)
return Promise.resolve()
})
.then(() => {
console.log(4)
})
Promise.resolve()
.then(() => {
console.log(1)
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(3)
})
.then(() => {
console.log(5)
})
.then(() => {
console.log(6)
})
看看能否分析出两者之间的细微差异?答案是不能。
更多请持续关注更文,或在参考链接中探索一二。
# 参考
- Promise/A+ 规范译文 (opens new window)
- 原生 Promise 和手写 Promise 的区别是什么? (opens new window)
- resolve 时序 (opens new window)
- V8 源码解读 Promise (opens new window)
# 🎉 写在最后
🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star (opens new window) ✨支持一下哦!
手动码字,如有错误,欢迎在评论区指正💬~
你的支持就是我更新的最大动力💪~
GitHub (opens new window) / Gitee (opens new window)、GitHub Pages (opens new window)、掘金 (opens new window)、CSDN (opens new window) 同步更新,欢迎关注😉~