前言
JavaScript 中的 Promise 诞生于 ES2015(ES6),是当下前端开发中特别流行的一种异步操作解决方案,也几乎是前端面试过程中的必考题。如果能够熟练运用 Promise,并深入理解 Promise 的运行原理,想必不论是在实际开发中还是在面试过程中,都能如鱼得水。
本篇文章我们就来一步步分析 Promise 的特征与用法,并最终实现一个包含以下 API 的 Promise:
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.all()
Promise.resolve()
Promise.reject()
TIPS:
具体实现步骤会在代码中通过注释说明。
为了避免代码重复,下面的每一步的实现过程都会忽略其他步骤的代码,只包含当前步骤的必要代码。
完整代码将会放在最后。
开始之前先回顾一下 Promise 的基本用法
-
创建 Promise
let asyncFunc = new Promise((resolve, rejcet) => { Math.random() > 0.5 ? resolve('fulfilled') : rejcet('rejected') })
-
处理 Promise 返回的状态(完成或者失败)
asyncFunc.then( res => { console.log(res) }, err => { console.log(err) } )
第一步:声明 Promise 的三种状态
我们知道 Promise 有三种状态,他们不受外界影响,而且一旦状态改变,就不会再变,任何时候都可以得到这个结果。这里先将它们枚举出来,后续会大量用到:
-
待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
const PENDING = 'pending'
-
已兑现(fulfilled): 意味着操作成功完成。
const FULFILLED = 'fulfilled'
-
已拒绝(rejected): 意味着操作失败。
const REJECTED = 'rejected'
第二步:实现 reject() 和 resolve()
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。它接受一个函数作为参数,该函数的两个参数分别是 resolve
和 reject
。它们也是函数。
// 以构造函数的形式实现
class MyPromise {
constructor(executor) {
// 利用 try/catch 捕获错误
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 定义 Promise 初始状态为 PENDING
status = PENDING
// resolve 后返回的数据
data = undefined
// reject 后返回的原因
reason = undefined
// 成功
resolve = data => {
// 一旦状态改变,就不能再变
if (this.status !== PENDING) return
// 更改状态
this.status = FULFILLED
// 保存数据
this.data = data
}
// 失败
reject = reason => {
// 一旦状态改变,就不能再变
if (this.status !== PENDING) return
// 更改状态
this.status = REJECTED
// 保存原因
this.reason = reason
}
}
第三步:实现 .then()
.then()
方法是 Promise 的核心之一,异步操作的成功或失败,都可以通过 .then()
添加的回调函数进行处理。并且它将继续返回一个 Promise 对象,这样可以通过多次调用 .then()
添加多个回调函数,它们会按照插入的顺序执行,形成链式调用(chaining)。
class MyPromise {
// resolve 的回调函数列表
successCallback = []
// reject 的回调函数列表
failureCallback = []
// 成功
resolve = data => {
// 依次调用成功回调
while (this.successCallback.length) {
this.successCallback.shift()(this.data)
}
}
// 失败
reject = reason => {
// 依次调用失败回调
while (this.failureCallback.length) {
this.failureCallback.shift()(this.reason)
}
}
// .then():处理 resolve 和 reject
then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
// 创建一个新的 Promise 并 return,以供链式调用
let promise = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 转换为 异步执行,用来获取 新的 promise
setTimeout(() => {
try {
let value = onResolved(this.data)
// 判断返回值是普通值还是 Promise
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let value = onRejected(this.reason)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
// 将回调函数存入数组中等待被执行
this.successCallback.push(() => {
setTimeout(() => {
try {
let value = onResolved(this.data)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
// 将回调函数存入数组中等待被执行
this.failureCallback.push(() => {
setTimeout(() => {
try {
let value = onRejected(this.reason)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise
}
}
第四步:实现 .catch()
实现 .then()
之后,.catch()
就会简单很多。事实上,.catch()
只是没有给 fulfilled 状态预留参数位置的 .then()
而已,所以这里我们直接返回一个没有成功回调函数的 .then()
即可。
class MyPromise {
// .catch()
catch(onRejected) {
// 事实上 .catch() 只是没有给 fulfilled 状态预留参数位置的 .then()
return this.then(undefined, onRejected)
}
}
第五步:实现 .finally()
有时候我们会在成功或失败执行相同的函数,为了避免了同样的语句需要在中各写一次的情况,所以有了.finally()
方法。也就是说,在 Promise 的结果无论是 fulfilled 或者是 rejected,.finally()
都会执行指定的回调函数。但区别于 .then()
和 .catch()
,.finally()
的回调函数中不接收任何参数。
class MyPromise {
// .finally()
finally(callback) {
return this.then(
data => {
return MyPromise.resolve(callback().then(() => data))
},
err => {
return MyPromise.resolve(callback()).then(() => {
throw err
})
}
)
}
}
第六步:实现 Promise.all()
Promise.all()
方法主要用于集合多个 Promise 的返回结果。
class MyPromise {
// Promise.all()
static all(iterable) {
// 记录执行次数
let times = 0
// 保存执行结果
let result = []
// Promise.all() 会返回一个 Promise
return new MyPromise((resolve, reject) => {
// 记录结果
function addData(key, value) {
times++
result[key] = value
times === iterable.length && resolve(result)
}
// 依次执行,然后将结果保存到数组中
iterable.forEach((element, index) => {
// 判断元素是否为 Promise 对象
element instanceof MyPromise
? element.then(
data => addData(index, data),
err => reject(err) // 任何一个 Promise 对象的 reject 被执行都会立即 reject()
)
: addData(index, element) // 非 promise 的元素将被直接放在返回数组中
})
})
}
}
第七步:实现 Promise.resolve()
Promise.resolve()
方法返回一个以给定值解析后的 Promise 对象。
class MyPromise {
// Promise.resolve()
static resolve(value) {
// 返回一个以给定值解析后的 Promise 对象
return value instanceof MyPromise
? value
: new MyPromise(resolve => resolve(value))
}
}
第八步:实现 Promise.reject()
Promise.reject()
方法返回一个带有拒绝原因的 Promise 对象。
class MyPromise {
// Promise.reject()
static reject(error) {
return new MyPromise((resolve, reject) => {
reject(error)
})
}
}
完整代码
// 首先,我们声明它的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 以构造函数的形式实现
class MyPromise {
constructor(executor) {
// 利用 try/catch 捕获错误
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 定义 Promise 初始状态为 PENDING
status = PENDING
// resolve 后返回的数据
data = undefined
// reject 后返回的原因
reason = undefined
// resolve 的回调函数列表
successCallback = []
// reject 的回调函数列表
failureCallback = []
// 成功
resolve = data => {
// 一旦状态改变,就不能再变
if (this.status !== PENDING) return
// 更改状态
this.status = FULFILLED
// 保存数据
this.data = data
// 依次调用成功回调
while (this.successCallback.length) {
this.successCallback.shift()(this.data)
}
}
// 失败
reject = reason => {
// 一旦状态改变,就不能再变
if (this.status !== PENDING) return
// 更改状态
this.status = REJECTED
// 保存原因
this.reason = reason
// 依次调用失败回调
while (this.failureCallback.length) {
this.failureCallback.shift()(this.reason)
}
}
// then:处理 resolve 和 reject
then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
// 创建一个新的 Promise 并 return,以供链式调用
let promise = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 转换为 异步执行,用来获取 新的 promise
setTimeout(() => {
try {
let value = onResolved(this.data)
// 判断返回值是普通值还是 Promise
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let value = onRejected(this.reason)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
// 将回调函数存入数组
this.successCallback.push(() => {
setTimeout(() => {
try {
let value = onResolved(this.data)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
// 将回调函数存入数组
this.failureCallback.push(() => {
setTimeout(() => {
try {
let value = onRejected(this.reason)
resolvePromise(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise
}
// .catch()
catch(onRejected) {
// 事实上 .catch() 只是没有给 fulfilled 状态预留参数位置的 .then()
return this.then(undefined, onRejected)
}
// .finally()
finally(callback) {
return this.then(
data => {
return MyPromise.resolve(callback().then(() => data))
},
err => {
return MyPromise.resolve(callback()).then(() => {
throw err
})
}
)
}
// Promise.all()
static all(iterable) {
// 记录执行次数
let times = 0
// 保存执行结果
let result = []
// Promise.all() 会返回一个 Promise
return new MyPromise((resolve, reject) => {
// 记录结果
function addData(key, value) {
times++
result[key] = value
times === iterable.length && resolve(result)
}
// 依次执行,然后将结果保存到数组中
iterable.forEach((element, index) => {
// 判断元素是否为 Promise 对象
element instanceof MyPromise
? element.then(
data => addData(index, data),
err => reject(err) // 任何一个 Promise 对象的 reject 被执行都会立即 reject()
)
: addData(index, element) // 非 promise 的元素将被直接放在返回数组中
})
})
}
// Promise.resolve()
static resolve(value) {
// 返回一个以给定值解析后的 Promise 对象
return value instanceof MyPromise
? value
: new MyPromise(resolve => resolve(value))
}
// Promise.reject()
static reject(error) {
return new MyPromise((resolve, reject) => {
reject(error)
})
}
}
// 判断 Promise 的返回值类型
function resolvePromise(promise, value, resolve, reject) {
// 循环调用报错
if (promise === value) {
return reject(
new TypeError('Chaining cycle detected for promise #')
)
}
// 如果是 Promise 对象
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
}
module.exports = MyPromise