前言
这可能是手写promise
较清晰的文章之一。
由浅至深逐步分析了原生测试用例,以及相关Promise/A+
规范。阅读上推荐以疑问章节为切入重点,对比Promise/A+
规范与ECMAScript
规范的内在区别与联系,确定怎样构建异步任务和创建promise
实例。然后开始手写章节,过程中代码与测试可参考 promise-coding 仓库。
也试着回答以下关键问题。
- 什么是广义对象?
- 如何检验
promise
类型? promise
与thenable
两类型有何区别?
疑问
如果不太清楚Promise
,建议参考《ECMAScript 6 入门》,预习下Promise
相关用法知识。
除此之外,对规范也要有大致认识,我们将根据几个疑问来细致阐述。
什么是 Promise/A+ 规范?
Promise
有多种社区规范,例如 Promise/A、Promise/B、Promise/D 和 Promises/KISS 等。
Promise/A+ 在Promise/A
之上,规范了术语并拓展了参数行为,省略了一些有问题的部分。
Promise/A+
有很多实现,例如第三方库 q、when 和 bluebird 等。实际上任何Promise
通过测试,都被认为是对Promise/A+
规范的一种实现。
Promise/A+
规范官方测试用例为 promises-aplus-tests
原生 Promise 实现了 Promise/A+?
在Promise/A+
规范 The ECMAScript Specification 章节中。
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 小结中明确了参数必须以异步形式运行。
2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
注解 3.1 补充可用宏任务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 创建微任务,兼容性相对较好
如何创建 promise?
Promise/A+
规范并未提及如何创建promise
。
ECMAScript6
规范发布以来,多数是以构造函数方式创建promise
。
new Promise(executor)
实际上在此之前,还流行一种Deferred
方式,例如 JQuery.Deferred。
$.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」
代码编写如下,其中属性[[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 允许实例属性定义在类内部的最顶层,相对更加清晰简洁
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 允许实例定义私有属性或方法,仅可在类内部使用,外部无法使用
状态不可变性
原生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」
- 处于等待态的时候,可能变为解决态或者拒绝态——「Promise/A+ 2.1.1」
代码优化为。
#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」onFulfilled
和onRejected
都是可选参数,若不是函数,必须被忽略——「Promise/A+ 2.2.1」onFulfilled
和onRejected
一定被作为普通函数调用——「Promise/A+ 2.2.5」
代码修改为。
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」
代码修改为。
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」规范,例如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 可选链操作符
多次调用 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」
代码优化如下,注意为了保证顺序,两数组内函数都是先进先出。
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」
代码暂时修改为。
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」 - 如果
x
不是一个对象或函数,用x
解决promise
——「Promise/A+ 2.3.4」
何为promise
解决程序呢?
「Promise/A+ 2.3」叙述是一个抽象操作,可表示为[[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」中各子章节。
修改代码并创建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」
代码优化为。
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」
代码简单修改为。
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」 - 如果
onRejected
不是一个函数且原promise
被拒绝,新promise
必须也被拒绝,且原因与原promise
相同——「Promise/A+ 2.2.7.4」
代码优化如下。
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」各子章节,以优化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」
代码优化为。
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」 - 如果
x
是解决态,用相同的值解决promise
——「Promise/A+ 2.3.2.2」 - 如果
x
是拒绝态,用相同的原因拒绝promise
——「Promise/A+ 2.3.2.3」
也就是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。
function isObject(value) {
const type = typeof value
return value !== null && (type === 'object' || type === 'function')
}
然后阅读规范「Promise/A+ 2.3.3」小节,省略部分暂时不考虑。
- 如果
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」小结陈述了。
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」提出了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。
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 仓库,支持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+ 规范译文
- 原生 Promise 和手写 Promise 的区别是什么?
- resolve 时序
- V8 源码解读 Promise
🎉 写在最后
🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!
手动码字,如有错误,欢迎在评论区指正💬~
你的支持就是我更新的最大动力💪~
GitHub / Gitee、GitHub Pages、掘金、CSDN 同步更新,欢迎关注😉~