一、规范
首先看看 Promise/A+
规范要求:
Promise 代表着异步操作的最终结果。与 promise 进行交互的主要方式是通过then
方法, 该方法通过注册回调以接收 promise 的最终值或 promise 未完成的原因。
-
promise 状态
pormise 必须是以下三个状态之一:pending,fulfilled,rejected。
- 当 promise 处于 pending 状态时:
- 可以转换到 fulfilled 或 rejected 状态。
- 当 promise 处于 fulfilled 状态时:
- 不能转换到其他状态。
- 必须有一个 value,并且不能改变。
- 当 promise 处于 rejected 状态时:
- 不能转换到其他状态。
- 必须有 reason ,并且不能改变。
- 当 promise 处于 pending 状态时:
-
then 方法
promise 必须提供一个 then 方法,能由此去访问当前或最终的 value 或者 reason。
pormise 的 then 方法, 接受两个参数
promise.then(onFulfilled, onRejected)
-
onFulfilled
和onRejected
都是可选参数。- 如果
onFulfilled
不是函数,则忽略。 - 如果
onRejected
不是函数,则忽略。
- 如果
-
如果
onFulfilled
是一个函数:- 它必须在
promise
被fulfilled
后,以promise
的value
作为第一个参数调用。 - 它不能在
promise
被fulfilled
之前调用。 - 它不能被调用多次。
- 它必须在
-
如果
onRejected
是一个函数:- 它必须在
promise
被rejected
后,以promise
的reason
作为第一个参数调用。 - 它不能能在
promise
被rejected
之前调用。 - 它不能被调用多次。
- 它必须在
-
在 execution context 栈(执行上下文栈)只包含引擎代码之前,
onFulfilled
或者onRejected
不能被调用. -
onFulfilled
或者onRejected
必须以函数形式调用(即不能有this
值) -
then
方法可以被同一个promise
调用多次。- 当
promise
处于fulfilled
状态, 所有自己的onFulfilled
回调函数,必须要按照then
注册的顺序被调用。 - 当
promise
处于rejected
状态, 所有自己的onRejected
回调函数,必须要按照then
注册的顺序被调用。
- 当
-
then
方法必须要返回promise
promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或者onRejected
返回一个值x
,则执行 Promise Resolution Procedure[[Resolve]](promise2, x)
. - 如果
onFulfilled
或者onRejected
抛出异常e
,promise2
必须以e
作为 reason ,转到 rejected 状态。 - 如果
onFulfilled
不是函数,并且promise1
处于 fulfilled 状态 ,则promise2
必须以与promise1
同样的 value 被 fulfilled . - 如果
onRejected
不是函数,并且promise1
处于 rejected 状态 ,则promise2
必须以与promise1
同样的 reason 被 rejected .
- 如果
-
-
Promise Resolution Procedure
Promise Resolution Procedure 是一个抽象操作。它以一个 promise 和一个 value 作为输入,记作:
[[Resolve]](promise, x)
。 如果x
是一个 thenable , 它会尝试让 promise 变成与 x 的一样状态 ,前提 x 是一个类似的 promise 对象。否则,它会让 promise 以x
作为 value 转为 fulfilled 状态。这种对 thenables 的处理允许不同的 promise 进行互操作,只要它们暴露一个符合 Promises/A+ 的 then 方法。它还允许 Promises/A+ 实现使用合理的 then 方法“同化”不一致的实现。
[[Resolve]](promise, x)
执行以下步骤:-
如果
promise
和x
引用的是同一个对象,则以一个TypeError
作为 reason 让promise
转为 rejeted 状态。 -
如果
x
也是一个 promise ,则让promise
接受它的状态- 如果
x
处于 pending 状态,promise
必须保持 pending 状态,直到x
变成 fulfilled 或者 rejected 状态,promise
才同步改变。 - 如果或者当
x
处于 fulfilled 状态, 以同样的 value 让promise
也变成 fulfilled 状态。 - 如果或者当
x
处于 rejected 状态, 以同样的 reason 让promise
也变成 rejected 状态。
- 如果
-
如果
x
是一个对象或者函数。-
令
then
等于x.then
. -
如果读取
x.then
抛出异常e
, 以e
作为 reason 让promise
变成 rejected 状态。 -
如果
then
是一个函数,以x
作为this
调用它,传入第一个参数resolvePromise
, 第二个参数rejectPromise
。-
如果
resolvePromise
被传入y
调用, 则执行[[Resolve]](promise, y)
-
如果
rejectedPromise
被传入r
调用,则用,r
作为 reason 让promise
变成 rejected 状态 -
如果
resolvePromise
和rejectPromise
都被调用了,或者被调用多次了。只有第一次调用生效,其余会被忽略。 -
如果调用
then
抛出异常e
,- 如果
resolvepromise
或rejectPromise
已经被调用过了,则忽略它。 - 否则, 以
e
作为 reason 让promise
变成 rejected 状态。
- 如果
-
-
如果
then
不是一个函数,以x
作为 value 让promise
变成 fulfilled 状态。
-
-
如果
x
不是对象或函数, 以x
作为 value 让promise
变成 fulfilled 状态。
-
如果一个 promise 被一个循环的 thenable 链中的对象 resolved,而 [[Resolve]](promise, thenable)
的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励实现者检测这样的递归是否存在,并且以 TypeError
作为 reason 拒绝 promise。
二、实现
- 使用 class 实现 Promise 类
- 使用 class 私有字段
- 使用
queueMicrotask
取代setTimeout
实现异步微任务
// promise.js
/* promise/A+ 规范 */
class Promise {
static #FULFILLED = 'fulfilled'
static #REJECTED = 'rejected'
static #PENDING = 'pending'
static #resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
throw new TypeError('Chaining cycle detected for promise')
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called
try {
const then = x.then
if (typeof then !== 'function') resolve(x)
else {
then.call(
x,
(value) => {
if (called) return
called = true
Promise.#resolvePromise(promise2, value, resolve, reject)
},
(reason) => {
if (called) return
called = true
reject(reason)
}
)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
#state = Promise.#PENDING
#result = null
#onResolveCallbacks = []
#onRejectCallbacks = []
constructor(executor) {
if (typeof executor !== 'function') {
return new TypeError(`Promise resolver ${executor} is not a function`)
}
try {
executor(this.#resolve.bind(this), this.#reject.bind(this))
} catch (e) {
this.#reject(e)
}
}
#resolve(value) {
if (this.#state === Promise.#PENDING) {
this.#state = Promise.#FULFILLED
this.#result = value
this.#onResolveCallbacks.forEach((cb) => cb())
}
}
#reject(reason) {
if (this.#state === Promise.#PENDING) {
this.#state = Promise.#REJECTED
this.#result = reason
this.#onRejectCallbacks.forEach((cb) => cb())
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => value
}
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw reason
}
}
const promise2 = new Promise((resolve, reject) => {
if (this.#state === Promise.#PENDING) {
this.#onResolveCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.#result)
Promise.#resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.#onRejectCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.#result)
Promise.#resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.#state === Promise.#FULFILLED) {
queueMicrotask(() => {
try {
const x = onFulfilled(this.#result)
Promise.#resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.#state === Promise.#REJECTED) {
queueMicrotask(() => {
try {
const x = onRejected(this.#result)
Promise.#resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
}
// 实现以下功能,并且执行 npx promises-aplus-tests
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
复制代码
三、验证
- 将文件保存为
promise.js
- 在目录执行以下命令验证
$ npx promises-aplus-tests promise.js
复制代码