# JavaScript Promise 详解
Promise 规范有很多,如 Promises/A、Promises/B、Promises/D 以及 Promises/A+。最终 ES6 中采用了 Promises/A+规范。
Promises/A+规范,有现成的单元测试套件,很容易搭建开发环境,以及验证代码是否符合规范要求。我们可以按照 Promises/A+ 规范,来从头实现一个 MyPromise。
# 1、Promise 的状态
一个 Promise 的状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)、拒绝态(Rejected)
# 等待态(Pending)
处于等待态时,promise 满足以下条件:
可以迁移至执行态或拒绝态
# 执行态(Fulfilled)
处于执行态时,promise 满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
# 拒绝态(Rejected)
处于拒绝态时, promise 满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的拒因
# 术语解释
promise:是一个包含 then 方法的对象或函数,该方法符合规范(即 Promises/A+)制定的行为。
thenable:是一个包含 then 方法的对象或者函数。
终值(eventual value):所谓的终值,指的是 promise 被解决时传递给解决回调的值,因为 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之为终值,有时候也直接简称为值(value),值可以是任何 JavaScript 合法值(包括 undefined、thenable 和 promise)
拒因(reason):也就是拒绝原因,指的是 promise 被拒绝时传递给拒绝回调的值。
解决(fulfill):指一个 promise 成功时(调用 resolve )进行的一系列操作,如状态的改变,回调的执行。虽然规范中用 fulfill 表示解决,但现在的 promise 实现中多以 resolve 指代之。
拒绝(reject): 指一个 promise 失败时(调用 reject 或抛出异常)进行的一系列操作。
exception(异常):就是 throw 语句抛出的值。
代码更新
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
# 2、Promise 构造函数
Promise 是一个构造函数,传参是一个函数,传入的函数接受两个参数,一个是解决回调 resolve,一个拒绝回调 reject。
代码更新
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
/** 更新 */
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
const resolve = (value) => {}
const reject = (reason) => {}
}
resolve 方法将 promise 的状态改为 Fulfilled ,并传入一个不可变的终值,reject 方法将 promise 的状态改为 Rejected,并传入一个不可变的拒因。promise 的状态一旦改为 Fulfilled 或者 Rejected 就不能再迁移至其他状态。
代码更新
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
// 终值
this.value = null
// 拒因
this.reason = null
const resolve = (value) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
}
const reject = (reason) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
}
}
调用构造函数的参数 fn,将 resolve 和 reject 作为参数传入 fn,记得加上 try,如果捕获到错误就 reject。
代码更新
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
// 终值
this.value = null
// 拒因
this.reason = null
const resolve = (value) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
}
const reject = (reason) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
}
/** 更新 */
// 如果有异常抛出,promise 就 reject
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
# 3、Then 方法
一个 promise 必须提供一个 then 实例方法以访问其终值和拒因。
promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected)
onFulfilled 和 onRejected 都是可选参数。
代码更新
MyPromise.prototype.then = function(onFulfilled, onRejected) {}
# onFulfilled
如果 onFulfilled 不是函数,其必须被忽略。所谓的“忽略”并不是什么都不干,对于 onFulfilled 来说,忽略就是将接收到的 value 直接返回。
如果 onFulfilled 是函数:
- 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
- 当 promise 执行结束前其不可被调用
- 其调用次数不可超过一次
# onRejected
如果 onRejected 不是函数,其必须被忽略。所谓的“忽略”并不是什么都不干,对于 onRejected 来说就是返回 reason,因为 onRejected 是一个拒绝分支,触发之后的拒绝状态,应该抛出一个异常,所以返回的 reason 应该 throw 一个 Error
如果 onRejected 是函数:
- 当 promise 被拒绝后其必须被调用,其第一个参数为 promise 的拒因
- 在 promise 被拒绝之前其不可被调用
- 其调用次数不可超过一次
# 调用时机
onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在then
方法被调用的那一轮事件循环之后的新执行栈中执行。
# 调用要求
onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)
# 多次调用
then 方法可以被同一个 promise 调用多次
- 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
- 当 promise 被拒绝时,所有的 onRejected 需按照其注册顺序依次回调
代码更新
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
// 终值
this.value = null
// 拒因
this.reason = null
/** 更新 */
// 成功后的 onFulfilled 的回调列表
this.onFulfilledCallbacks = []
// 被拒绝后的 onRejected 的回调列表
this.onRejectedCallbacks = []
const resolve = (value) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// resolve 时把所有成功的回调拿出来执行
this.onFulfilledCallbacks.forEach((callback) => {
callback(this.value)
})
}
const reject = (reason) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// reject时把所有失败的回调拿出来执行
this.onRejectedCallbacks.forEach((callback) => {
callback(this.reason)
})
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,就给一个默认函数,返回 vallue
let realOnFulfilled = onFulfilled
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function(value) {
return value
}
}
// 如果 onRejected 不是函数,就给一个默认函数,抛出异常
let realOnRejected = onRejected
if (typeof realOnRejected !== 'function') {
realOnRejected = function(reason) {
throw reason
}
}
// 下面一步一步实现规范中的其他描述
// 1、参数检查完后,按照规范,如果promise执行成功就会调用 onFulfilled,如果失败了,就会调用 onRejected 。在之后的代码里,我们就应该检查 promise 的 status,如果是 FULFILLED,就调用 onFulfilled,如果是 REJECTED,就调用 onRejected
// 2、onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。意思是实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行,所以我们执行 onFulfilled 和 onRejected 的时候都应该包到 setTimeout 里面去。
if (this.status === FULFILLED) {
setTimeout(() => {
realOnFulfilled(this.value)
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
realOnRejected(this.reason)
}, 0)
}
// 2、then 一般是在实例对象一创建好就调用了,这时候构造函数的传参 fn 中可能有异步操作还没结束,也就是说 promise 的状态还是 PENDING,还不知道 promise 到底是成功还是失败。那什么时候知道 promise 成功还是失败呢?答案是 fn 里面主动调用 resolve 或者 reject 的时候。所以如果 promise 的 status 还是 PENDING,就应该将 onFulfilled 和 onRejected 两个回调保存起来,等 fn 有了结论,主动调用了 resolve 或 reject 的时候再来调用对应的回调。规范中还提到 then 方法可以被同一个 promise 调用多次,所以会有多个 onFulfilled 和 onRejected,我们可以用两个数组将它们存起来,等 resolve 或者 reject 的时候将数组里面的回调函数循环执行一遍
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
realOnFulfilled(this.value)
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
realOnRejected(this.reason)
}, 0)
})
}
}
# 返回
then 方法必须返回一个新的 promise 对象
promise2 = promise1.then(onFulfilled, onRejected)
- 如果 onFulfilled 或者 onRejected 返回一个值 x,则运行下面的Promise 解决过程:
[[Resolve]](promise2, x)
- 如果 onFulfilled 或者 onRejected 抛出一个异常 e,则 promise2 必须被拒绝,并返回拒因 e
- 如果 onFulfilled 不是函数且 promise1 成功执行,promise2 必须成功执行并返回相同的值
- 如果 onRejected 不是函数且 promise1 被拒绝,promise2 必须被拒绝并返回相同的拒因。
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
// 终值
this.value = null
// 拒因
this.reason = null
// 成功后的 onFulfilled 的回调列表
this.onFulfilledCallbacks = []
// 被拒绝后的 onRejected 的回调列表
this.onRejectedCallbacks = []
const resolve = (value) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// resolve 时把所有成功的回调拿出来执行
this.onFulfilledCallbacks.forEach((callback) => {
callback(this.value)
})
}
const reject = (reason) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// reject时把所有失败的回调拿出来执行
this.onRejectedCallbacks.forEach((callback) => {
callback(this.reason)
})
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,就给一个默认函数,返回 value
let realOnFulfilled = onFulfilled
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function(value) {
return value
}
}
// 如果 onRejected 不是函数,就给一个默认函数,抛出异常
let realOnRejected = onRejected
if (typeof realOnRejected !== 'function') {
realOnRejected = function(reason) {
throw reason
}
}
// 更新1、参数检查完后,按照规范,如果 promise 执行成功就会调用 onFulfilled,如果失败了,就会调用 onRejected 。在之后的代码里,我们就应该检查 promise 的 status,如果是 FULFILLED,就调用 onFulfilled,如果是 REJECTED,就调用 onRejected
/** 根据规范 then 的返回值必须是一个 promise,规范还定义了不同情况应该怎么处理,先从简单的开始 */
// 更新2、如果 onFulfilled 或者 onRejected 抛出一个异常 e,则 promise2 必须被拒绝,并返回拒因 e 。所以在 FULFILLED 和 REJECTED 的时候就不能简单的运行 onFulfilled 和 onRejected 了,需要将它们用 try...catch... 包起来,如果有错就 reject 了。
// 更新3、如果 onFulfilled 或者 onRejected 返回一个值 x,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)。上面的代码中,只要 onRejected 或者 onFulfilled 成功执行了,我们都要 resolve promise2 。多了这一条我们还需要对 onRejected 或者 onFulfilled 的返回值进行判断,如果有返回值就要进行 Promise 解决过程。我们可以专门实现一个 resolvePromise 方法来进行 Promise 的解决过程。前面的代码实现,其实只要 onRejected 或者 onFulfilled 成功执行了,我们都需要 resolve promise2,这个过程可以放到这个方法里面。
if (this.status === FULFILLED) {
var promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.value)
} else {
var x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject) // 调用Promise解决过程
}
} catch (error) {
reject(error)
}
}, 0)
})
return promise2
}
// 更新2、如果onRejected不是函数且promise1被拒绝,promise2必须拒绝并返回相同的拒因
if (this.status === REJECTED) {
var promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
// 注意,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve,并且 onRejected 有返回值,就需要执行 resolvePromise
var x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
return promise2
}
// 2、then 一般是在实例对象一创建好就调用了,这时候构造函数的传参fn中可能有异步操作还没结束,也就是说 promise 的状态还是 PENDING,还不知道 promise 到底是成功还是失败。那什么时候知道 promise 成功还是失败呢?答案是 fn 里面主动调用 resolve 或者 reject 的时候。所以如果 promise 的 status 还是 PENDING,就应该将 onFulfilled 和 onRejected 两个回调保存起来,等 fn 有了结论,主动调用了 resolve 或 reject 的时候再来调用对应的回调。规范中还提到 then 方法可以被同一个 promise 调用多次,所以会有多个 onFulfilled 和 onRejected,我们可以用两个数组将它们存起来,等 resolve 或者 reject 的时候将数组里面的回调函数循环执行一遍
// 同上、更新2、更新3、如果还是 PENDING 状态,也不能直接保存回调方法,需要包一层来捕获错误。有返回值 x,就运行 Promise 的解决过程
if (this.status === PENDING) {
var promise2 = new MyPromise((resolve, reject) => {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.value)
} else {
var x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
// 注意,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve,并且 onRejected 有返回值,就需要执行 resolvePromise
var x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
})
return promise2
}
}
# Promise 解决过程
onFulfilled 或者 onRejected 返回一个值 x,则运行 Promise 解决过程。
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值 x,我们表示为[[Resolve]](promise2, x)
,如果 x 有 then 方法,且看上去像一个 Promise(thenable),解决程序及尝试使 promise2 接受 x 的状态;否则其用 x 的值来执行(resolve) promise2。
运行[[Resolve]](promise2, x)
需遵循以下步骤:
# x 与 promise2 相等
如果 promise2 和 x 指向同一对象,以 TypeError 为拒因拒绝 promise2。
# x 为 Promise
如果 x 为 Promise,则使 promise2 接受 x 的状态:
- 如果 x 处于等待态,promise2 需保持为等待态直至 x 被执行或拒绝
- 如果 x 处于执行态,用相同的值执行 promise2
- 如果 x 处于拒绝态,用相同的拒因拒绝 promise2
# x 为对象或函数
如果 x 为对象或者函数:
- 把
x.then
赋值给 then - 如果取 x.then 的值时抛出错误 e,则以 e 为拒因拒绝 promise2
- 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise:
- 如果 resolvePromise 以值 y 为参数被调用,则运行
[[Resolve]](promise2, y)
- 如果 rejectPromise 以拒因 r 为参数被调用,则以拒因 r 拒绝 promise2
- 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
- 如果调用 then 方法抛出了异常 e:
- 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
- 否则以 e 为拒因拒绝 promise2
- 如果 resolvePromise 以值 y 为参数被调用,则运行
- 如果 then 不是函数,以 x 为参数执行 promise2
# 如果 x 不为对象或者函数,以 x 为参数执行 promise2
/** Promise 解决过程 */
function resolvePromise(promise2, x, resolve, reject) {
// 如果 promise2 和 x 指向同一对象,以 TypeError 为拒因拒绝 promise2
if (promise2 === x) {
return reject(
new TypeError('The promise and the return value are the same')
)
}
// 如果 x 为 Promise,则使 promise2 接受 x 的状态,也就是继续执行 x,如果解决回调执行的时候拿到一个 y,还需要继续解析 y
if (x instanceof MyPromise) {
x.then(function(y) {
resolvePromise(promise2, y, resolve, reject)
}, reject)
} else if (
Object.prototype.toString.call(x) === '[object Object]' ||
typeof x === 'function'
) {
try {
// 把 x.then 赋值给 then
var then = x.then
} catch (error) {
// 如果取 x.then 的值时抛出错误 e,则以 e 为拒因拒绝 promise
return reject(error)
}
if (typeof then === 'function') {
var called = false
// 将 x 作为函数的作用域 this 调用之,传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise,就是一个名字而已,可以直接传入的两个匿名函数
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise2, y)
function(y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量 called
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
// 如果 rejectPromise 以拒因 r 为参数被调用
function(r) {
if (called) return
called = true
reject(r)
}
)
} catch (error) {
// 如果调用 then 方法抛出异常 e
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return
//否则以 e 为拒因拒绝 promise2
reject(error)
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise2
resolve(x)
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise2
resolve(x)
}
}
# 最终代码汇总
const PENDING = 'Pending'
const FULFILLED = 'Fulfilled'
const REJECTED = 'Rejected'
function MyPromise(fn) {
// 初始状态,可以迁移至其他任何状态
this.status = PENDING
// 终值
this.value = null
// 拒因
this.reason = null
// 成功后的 onFulfilled 的回调列表
this.onFulfilledCallbacks = []
// 被拒绝后的 onRejected 的回调列表
this.onRejectedCallbacks = []
const resolve = (value) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// resolve 时把所有成功的回调拿出来执行
this.onFulfilledCallbacks.forEach((callback) => {
callback(this.value)
})
}
const reject = (reason) => {
// 如果状态不为 Pending,不能再迁移至其他状态
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// reject时把所有失败的回调拿出来执行
this.onRejectedCallbacks.forEach((callback) => {
callback(this.reason)
})
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,就给一个默认函数,返回 value
let realOnFulfilled = onFulfilled
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function(value) {
return value
}
}
// 如果 onRejected 不是函数,就给一个默认函数,抛出异常
let realOnRejected = onRejected
if (typeof realOnRejected !== 'function') {
realOnRejected = function(reason) {
throw reason
}
}
// 更新1、参数检查完后,按照规范,如果 promise 执行成功就会调用 onFulfilled,如果失败了,就会调用 onRejected 。在之后的代码里,我们就应该检查 promise 的 status,如果是 FULFILLED,就调用 onFulfilled,如果是 REJECTED,就调用 onRejected
/** 根据规范 then 的返回值必须是一个 promise,规范还定义了不同情况应该怎么处理,先从简单的开始 */
// 更新2、如果 onFulfilled 或者 onRejected 抛出一个异常 e,则 promise2 必须被拒绝,并返回拒因 e 。所以在 FULFILLED 和 REJECTED 的时候就不能简单的运行 onFulfilled 和 onRejected 了,需要将它们用 try...catch... 包起来,如果有错就 reject 了。
// 更新3、如果 onFulfilled 或者 onRejected 返回一个值 x,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)。上面的代码中,只要 onRejected 或者 onFulfilled 成功执行了,我们都要 resolve promise2 。多了这一条我们还需要对 onRejected 或者 onFulfilled 的返回值进行判断,如果有返回值就要进行 Promise 解决过程。我们可以专门实现一个 resolvePromise 方法来进行 Promise 的解决过程。前面的代码实现,其实只要 onRejected 或者 onFulfilled 成功执行了,我们都需要 resolve promise2,这个过程可以放到这个方法里面。
if (this.status === FULFILLED) {
var promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.value)
} else {
var x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject) // 调用Promise解决过程
}
} catch (error) {
reject(error)
}
}, 0)
})
return promise2
}
// 更新2、如果onRejected不是函数且promise1被拒绝,promise2必须拒绝并返回相同的拒因
if (this.status === REJECTED) {
var promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
// 注意,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve,并且 onRejected 有返回值,就需要执行 resolvePromise
var x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
return promise2
}
// 2、then 一般是在实例对象一创建好就调用了,这时候构造函数的传参fn中可能有异步操作还没结束,也就是说 promise 的状态还是 PENDING,还不知道 promise 到底是成功还是失败。那什么时候知道 promise 成功还是失败呢?答案是 fn 里面主动调用 resolve 或者 reject 的时候。所以如果 promise 的 status 还是 PENDING,就应该将 onFulfilled 和 onRejected 两个回调保存起来,等 fn 有了结论,主动调用了 resolve 或 reject 的时候再来调用对应的回调。规范中还提到 then 方法可以被同一个 promise 调用多次,所以会有多个 onFulfilled 和 onRejected,我们可以用两个数组将它们存起来,等 resolve 或者 reject 的时候将数组里面的回调函数循环执行一遍
// 同上、更新2、更新3、如果还是 PENDING 状态,也不能直接保存回调方法,需要包一层来捕获错误。有返回值 x,就运行 Promise 的解决过程
if (this.status === PENDING) {
var promise2 = new MyPromise((resolve, reject) => {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.value)
} else {
var x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
// 注意,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve,并且 onRejected 有返回值,就需要执行 resolvePromise
var x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
} catch (error) {
reject(error)
}
}, 0)
})
})
return promise2
}
}
/** Promise 解决过程 */
function resolvePromise(promise2, x, resolve, reject) {
// 如果 promise2 和 x 指向同一对象,以 TypeError 为拒因拒绝 promise2
if (promise2 === x) {
return reject(
new TypeError('The promise and the return value are the same')
)
}
// 如果 x 为 Promise,则使 promise2 接受 x 的状态,也就是继续执行 x,如果解决回调执行的时候拿到一个 y,还需要继续解析 y
if (x instanceof MyPromise) {
x.then(function(y) {
resolvePromise(promise2, y, resolve, reject)
}, reject)
} else if (
Object.prototype.toString.call(x) === '[object Object]' ||
typeof x === 'function'
) {
try {
// 把 x.then 赋值给 then 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用,所以在此处取出来
var then = x.then
} catch (error) {
// 如果取 x.then 的值时抛出错误 e,则以 e 为拒因拒绝 promise
return reject(error)
}
if (typeof then === 'function') {
var called = false
// 将 x 作为函数的作用域 this 调用之,传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise,就是一个名字而已,可以直接传入的两个匿名函数
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise2, y)
function(y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量 called
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
// 如果 rejectPromise 以拒因 r 为参数被调用
function(r) {
if (called) return
called = true
reject(r)
}
)
} catch (error) {
// 如果调用 then 方法抛出异常 e
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return
//否则以 e 为拒因拒绝 promise2
reject(error)
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise2
resolve(x)
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise2
resolve(x)
}
}
# 其他 Promise 的方法
ES6 的 Promise 还有很多 API,这些 API 都可以用我们按照 promise/A+规范实现的代码,再进行封装得到。
# Promise.resolve
返回一个以给定值解析后的 Promise 实例,换句话说就是将传入的值转换为 Promise 对象。如果传参是 Promise,直接返回,如果是 thenable 对象,返回的 Promise 实例会采用 thenable 的运行后的状态,如果不是前面两种类型,那就返回一个新的 Promise 实例,状态为 Fulfilled。
MyPromise.resolve = (parameter) => {
// 如果传参是一个 Promise,直接返回这个 Promise
if (parameter instanceof MyPromise) {
return parameter
}
// 简单起见,直接把后两种情况一起处理
var promise = new MyPromise((resolve) => {
resolve(parameter)
})
return promise.then((value) => {
return value
})
}
# Promise.reject
返回一个新的 Promise 实例,该实例的状态为 Rejected。Promise.reject 方法的参数 reason,会被传递给实例的回调函数。
MyPromise.reject = (reason) => {
return new MyPromise((resolve, reject) => {
reject(reasion)
})
}
# Promise.all
该方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
该方法接收的参数是 promise 的 iterate 类型(例如:Array、Map、Set),一般是一个数组,并且只返回一个 Promise 实例,这个实例的 resolve 回调结果是一个数组。必须注意:返回的这个数组每一项的顺序将会按照传入时参数的 promise 顺序排列,而不是由调用 promise 的完成顺序决定
传入参数 iterate 类型中每一项都是 promise 实例,如果不是,就会先调用 Promise.resolve 方法转为 Promise 实例,再进一步处理。当 iterate 类型的每一项都 resolve,返回的 promise 才 resolve,有任何一个 reject ,返回的 promise 就 reject,并且 reject 的是第一个抛出的错误信息。
MyPromise.all = (promiseList) => {
let result = []
let count = 0
let length = promiseList.length
let combinePromise = new MyPromise((resolve, reject) => {
// 如果传入的参数是一个空的可迭代对象,直接返回一个 Fulfilled 状态的 Promise
if (length === 0) {
return resolve(result)
}
// 其他情况下返回的是一个 Pending 的 Promise,这个返回的 promise,之后会在所有 promise 都完成或有一个 promise 失败时*异步*地变为完成或失败
promiseList.forEach((promise, index) => {
MyPromise.resolve(promise).then(
(value) => {
count++
// 将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定
result[index] = value
if (count === length) {
resolve(result)
}
},
(reason) => {
reject(reason)
}
)
})
})
return combinePromise
}
# Promise.race
该方法同样也是将多个 Promise 实例包装成一个新的 Promise 实例。
Promise.race(iterate) 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
MyPromise.race = (promiseList) => {
let length = promiseList.length
return new MyPromise((resolve, reject) => {
if (length === 0) {
return resolve()
}
promiseList.forEach((promise) => {
MyPromise.resolve(promise).then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
# Promise.allSettled
ES2020 引入,该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数示例都返回结果,不管是 fulfilled 还是 rejected ,新的 Promise 才会结束,一旦结束,状态总是 fulfilled, 不会变成 rejected。状态变成 fulfilled 后,解决回调函数接收到的参数是一个数组,每一项表示对应的 promise 的结果,并且每一项的格式为 {status: "fulfilled", value: xxx} 或 {status: "rejected" reason: xxx}
MyPromise.allSettled = (promiseList) => {
let mapPromises = promiseList.map((promise) => {
return MyPromise.resolve(promise).then(
(value) => {
return {
status: 'fulfilled',
value,
}
},
(reason) => {
return {
status: 'rejected',
reason,
}
}
)
})
return MyPromise.all(mapPromises)
}
# Promise.any
ES2021 引入,该方法接受一个 promise 可迭代对象,只要其中一个 promise 成功,就返回那个已经成功的 promise,如果所有的 promise 都失败了,就返回一个失败的 promise,拒因为一个把单个错误合在一起的 AggregateError。本质上,此方法跟 Promise.all 相反。
MyPromise.any = (promiseList) => {
return MyPromise.all(
promiseList.map((promise) => {
// 如果一个promise失败,就将其转换成已解决,等待之后可能的成功,如果其中一个promise成功了,将其转换成拒绝,Promise.all会立即退出。
return promise.then(
(value) => MyPromise.reject(value),
(reason) => MyPromise.resolve(reason)
)
})
).then(
// 如果 '.all' 被解决,我们就得到了一个 errors 的数组
(errors) => MyPromise.reject(errors),
// 如果 '.all' 被拒绝,我们就得到了第一个成功的promise的结果
(value) => MyPromise.resolve(value)
)
}
# Promise.prototype.catch
Promise.prototype.catch
方法是 .then(null, onRejected)
或 .then(undefined, onRejected)
的别名,用于指定发生错误时的回调函数。
如果 onRejected 抛出一个错误或返回一个本身失败的 Promise,通过 catch() 返回的 Promise 会被 reject;否则,返回的 Promise 会被 resolve。
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
# Promise.prototype.finally
用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入的。
MyPromise.prototype.finally = function(callback) {
return this.then(
(value) => {
return MyPromise.resolve(callback()).then(() => {
return value
})
},
(reason) => {
return MyPromise.resolve(callback()).then(() => {
throw reason
})
}
)
}
# 一些自己实现的其他的方法
// 实现 cancel 和 stop ,用来停止一个 Promise 链
MyPromise.cancel = MyPromise.stop = function() {
return new MyPromise(function() {})
}
// Promise 链上返回的最后一个 Promise 出错了,就会被内部的 try...catch 吞掉,可以实现一个done方法
Promise.prototype.done = function() {
return this.catch(function(e) {
// 此处一定要确保这个函数不能再出错
console.error(e)
})
}
# 测试
我们可以使用 Promise/A+官方的测试工具 promises-aplus-tests 来对自己实现的 MyPromise 进行测试,要使用这个工具我们必须实现一个静态方法 deferred :
deferred:返回一个包含 {promise, resolve, reject} 的对象 promise 是一个处于 pending 状态的 promise resolve(value) 用 value 解决上面那个 promise reject(reason) 用 reason 拒绝上面那个 promise
新建一个 adater.js 文件:
const MyPromise = require('./my-promise')
MyPromise.deferred = () => {
let promise, resolve, reject
promise = new MyPromise(($resolve, $reject) => {
resolve = $resolve
reject = $reject
})
return { promise, resolve, reject }
}
module.exports = MyPromise
然后再 npm install promises-aplus-tests
, 在配置好 package.json :
{
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"test": "promises-aplus-tests adater"
}
}
npm run test
就可以跑测试了
# 注意
# ES6 Promise 中 resolve 传值
ES6 的 Promise,在调用 resolve,传入一个 promise 实例时,即使在 onFulfilled 中不返回这个实例,后续的链式操作,也会以这个传入的实例状态为准,而我们自己实现的 MyPromise 必须在 onFulfilled 中返回传入的实例,走到 resolvePromise 环节,后续的链式操作才会以传入的实例状态为准。
const p1 = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function(resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then((result) => console.log(result)).catch((error) => console.log(error))
// Error: fail
# 推荐文章
- Promises/A+规范 (opens new window)
- 手写一个 Promise/A+,完美通过官方 872 个测试用例 (opens new window)
- 手把手教你实现 Promise(主要是前面几个状态图可以看一下) (opens new window)
- 你真的懂 Promise 吗 (opens new window)
- 从 Promise 的 Then 说起 (opens new window)
- 100 行代码实现 Promises/A+ 规范 (opens new window)
- 剖析 Promise 内部结构,一步一步实现一个完整的、能通过所有 Test case 的 Promise 类 (opens new window)