简单手写 Promise
在刚学 JS 的时候有尝试手写过完整的 Promise,但是代码量太过于庞大了,过了半年就忘记了。 这一次,手写一个简易版的 Promise,以加强理解。
技术要点
- 能够初始化和异步调用
- 能够实现 then 和 catch 的链式调用
- 静态方法: resolve、reject、all 和 race
提示
值得注意的是, 本文实现的 Promise, 并非规范版 Promise。区别在于: 规范中不论 promise 被 reject 还是被 resolve 时, 结果 都会被 resolve,只有出现异常时才会被rejected
。
测试用例
首先准备测试用例:
// 常规方法/异步调用
const p1 = new MyPromise((resolve, reject) => {
resolve(100)
})
console.log('p1', p1)
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(200)
}, 100)
})
console.log('p2', p2)
const p3 = new MyPromise((resolve, reject) => {
reject(300)
})
console.log('p3', p3)
const p4 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(400)
}, 100)
})
console.log('p4', p4)
// 链式调用 then catch
const p5 = p1.then((res) => {
return res + 1
})
console.log('p5', p5)
const p6 = p3.catch((data) => {
return data + 2
})
console.log('p6', p6)
// 静态方法
const p7 = MyPromise.resolve(700)
const p8 = MyPromise.reject('错误信息')
const p9 = MyPromise.all([p1, p2, p7, p8])
const p10 = MyPromise.race([p1, p2, p7, p8])
正式手写 Promise
1. 手写基础构造函数
首先,定义一个名为 MyPromise 的类。该类包含了三个属性:state、value 和 reason。state 表示 Promise 的状态,可能的取值有 'padding'(进行中)、'fulfilled'(已成功)和 'rejected'(已失败)。value 是成功时的返回值,reason 是失败时的原因。
类的构造函数接受一个函数 fn 作为参数,该函数包含两个参数 resolveHandler 和 rejectHandler,分别用于处理成功和失败的情况。
在构造函数内部,定义 resolveHandler 和 rejectHandler 函数。resolveHandler 用于将 Promise 的状态从 'padding' 改变为 'fulfilled',并将返回值保存在 value 属性中。rejectHandler 用于将 Promise 的状态从 'padding' 改变为 'rejected',并将失败原因保存在 reason 属性中。
接下来,即调用传入构造函数的函数 fn,尝试执行 fn(resolveHandler, rejectHandler),并将定义的 resolveHandler 和 rejectHandler 作为参数传递给它。这表示 fn 函数中的代码将决定 Promise 的最终状态和值。如果 fn 执行成功,调用 resolveHandler 函数将使 Promise 变为成功状态,并将返回值保存在 value 属性中。如果 fn 抛出异常,catch 块将捕获异常并调用 rejectHandler 函数,将异常作为失败原因保存在 reason 属性中。
具体代码如下:
class MyPromise {
state = 'padding' // 状态, 'padding' 'fulfilled' 'rejected'
value = undefined // 成功之后的值
reason = undefined // 失败之后的值
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'padding') {
this.state = 'fulfilled'
this.value = value
}
}
const rejectHandler = (reason) => {
if (this.state === 'padding') {
this.state = 'rejected'
this.reason = reason
}
}
// Promise 只能捕获同步错误, 无法捕获异步错误
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
}
关于异步错误的捕获,官方的 Promise 也是无法捕获的:
new Promise(() => {
setTimeout(() => {
throw '错误'
}, 100)
})
// 结果:
// Promise {<pending>}
2. 实现 then 和 catch 的链式调用
首先,在 MyPromise 类中添加了两个属性 resolveCallBacks 和 rejectCallBacks,分别用于存储在状态为 'padding' 时的成功回调和失败回调。这意味着在 Promise 处于进行中状态时,可以通过 then() 方法注册成功和失败的回调函数。
接下来,定义 then() 方法,该方法接受两个参数 fn1 和 fn2,分别表示成功和失败的回调函数。这两个参数可以是函数,也可以是非函数值。如果参数不是函数,将被转换为一个函数,该函数会将传入的值直接返回。
在 then() 方法内部,根据 Promise 的当前状态进行不同的处理:
- 如果状态为 'padding',说明 Promise 仍处于进行中状态,无法立即转换状态。此时,将返回一个新的 MyPromise 对象,并将成功和失败的回调函数存储到对应的回调数组中(resolveCallBacks 和 rejectCallBacks)。在异步操作完成后,通过执行这些回调函数来转换状态和传递值。
- 如果状态为 'fulfilled',说明 Promise 已经成功。此时,将立即执行成功回调函数 fn1,并返回一个新的 MyPromise 对象,该对象的状态由 fn1 的执行结果决定。
- 如果状态为 'rejected',说明 Promise 已经失败。此时,将立即执行失败回调函数 fn2,并返回一个新的 MyPromise 对象,该对象的状态由 fn2 的执行结果决定。
最后,依据 Promise 的定义可以知道 catch() 方法实际上就是 then() 方法的语法糖。catch() 方法接受一个参数 fn,表示失败的回调函数。实际上,它相当于调用 then(null, fn),将 null 作为成功回调函数,将 fn 作为失败回调函数。
在下述代码中,resolveCallBacks 和 rejectCallBacks 是使用数组保存的,目的在于连续调用 then 时以确保 then 都能得到正确执行:
const p = new Promise((resolve, > reject)=>{ setTimeout(()=>{ resolve(100) }, 0) }) p.then((res)=>{ // 第一次调用: 100 res1 console.log(res, 'res1') }) p.then((res2)=>{ // 第二次调用: 100 res2 console.log(res2, 'res2') })
因此,可以写出如下的代码:
class MyPromise {
state = 'padding' // 状态, 'padding' 'fulfilled' 'rejected'
value = undefined // 成功之后的值
reason = undefined // 失败之后的值
resolveCallBacks = [] // padding 状态下, 存储成功的回调
rejectCallBacks = [] // padding 状态下, 存储失败的回调
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'padding') {
this.state = 'fulfilled'
this.value = value
this.resolveCallBacks.forEach((fn) => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'padding') {
this.state = 'rejected'
this.reason = reason
this.rejectCallBacks.forEach((fn) => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
// 传入非函数值, 则会无效化
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'padding') {
// 当 padding 状态下, 则是异步调用无法立即装换状态,fn1 和 fn2 会存储到 CallBacks 中
return new MyPromise((resolve, reject) => {
this.resolveCallBacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue) // 异步执行完毕得到 newValue 时,才转换 state 状态
} catch (error) {
reject(error)
}
})
this.rejectCallBacks.push((resolve, reject) => {
try {
const newReason = fn2(this.reason)
reject(newReason) // 异步执行完毕得到 newReason 时,才转换 state 状态
} catch (error) {
reject(error)
}
})
})
}
if (this.state === 'fulfilled') {
return new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (error) {
reject(error)
}
})
}
if (this.state === 'rejected') {
return new MyPromise((resolve, rejected) => {
try {
const newReason = fn2(this.reason)
rejected(newReason)
} catch (error) {
rejected(error)
}
})
}
}
// catch 是 then 的语法糖, resolve 回调函数为 null
catch(fn) {
return this.then(null, fn)
}
}
3. 完善 Promise 的静态方法
Promise 的静态方法有很多,这里着重实现几个:
- MyPromise.resolve(value) 方法接受一个值 value,返回一个已经被解析为成功状态的 Promise 对象。它实际上是创建一个新的 MyPromise 对象,并立即调用 resolve 方法将 value 作为成功值传递。
- MyPromise.reject(reason) 方法接受一个原因 reason,返回一个已经被解析为失败状态的 Promise 对象。它实际上是创建一个新的 MyPromise 对象,并立即调用 reject 方法将 reason 作为失败原因传递。
- MyPromise.all(promiseList) 方法接受一个 Promise 对象数组 promiseList,返回一个新的 Promise 对象。这个方法将等待所有的 Promise 对象都解析为成功状态,然后将所有 Promise 对象的结果组成一个数组作为新 Promise 对象的成功值。如果其中任何一个 Promise 对象失败,新的 Promise 对象将立即被解析为失败状态,并传递失败原因。
- MyPromise.race(promiseList) 方法接受一个 Promise 对象数组 promiseList,返回一个新的 Promise 对象。这个方法将等待任何一个 Promise 对象首先解析为成功或失败状态,然后将该 Promise 对象的结果作为新 Promise 对象的结果。无论是成功还是失败,新的 Promise 对象将立即解析。
MyPromise.resolve = function (value) {
return new MyPromise((resolve, rejected) => resolve(value))
}
MyPromise.reject = function (reason) {
return new MyPromise((resolve, rejected) => rejected(reason))
}
MyPromise.all = function (promiseList = []) {
return new MyPromise((resolve, rejected) => {
const result = [] // 储存所有的 Promise
const length = promiseList.length
let resolvedCount = 0
promiseList.forEach((p) => {
p.then((data) => {
result.push(data)
if (++resolvedCount === length) {
resolve(result)
}
}).catch((err) => {
rejected(err)
})
})
})
}
MyPromise.race = function (promiseList = []) {
let resolved = false
return new MyPromise((resolve, reject) => {
promiseList.forEach((p) => {
p.then((data) => {
if (!resolved) {
resolve(data)
resolved = true
}
}).catch((err) => {
reject(err)
})
})
})
}
完整 Promise 代码
class MyPromise {
state = 'padding' // 状态, 'padding' 'fulfilled' 'rejected'
value = undefined // 成功之后的值
reason = undefined // 失败之后的值
resolveCallBacks = [] // padding 状态下, 存储成功的回调
rejectCallBacks = [] // padding 状态下, 存储失败的回调
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'padding') {
this.state = 'fulfilled'
this.value = value
this.resolveCallBacks.forEach((fn) => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'padding') {
this.state = 'rejected'
this.reason = reason
this.rejectCallBacks.forEach((fn) => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
// 传入非函数值, 则会无效化
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'padding') {
// 当 padding 状态下, 则是异步调用无法立即装换状态,fn1 和 fn2 会存储到 CallBacks 中
return new MyPromise((resolve, reject) => {
this.resolveCallBacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue) // 异步执行完毕得到 newValue 时,才转换 state 状态
} catch (error) {
reject(error)
}
})
this.rejectCallBacks.push((resolve, reject) => {
try {
const newReason = fn2(this.reason)
reject(newReason) // 异步执行完毕得到 newReason 时,才转换 state 状态
} catch (error) {
reject(error)
}
})
})
}
if (this.state === 'fulfilled') {
return new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (error) {
reject(error)
}
})
}
if (this.state === 'rejected') {
return new MyPromise((resolve, rejected) => {
try {
const newReason = fn2(this.reason)
rejected(newReason)
} catch (error) {
rejected(error)
}
})
}
}
// catch 是 then 的语法糖 resolve 回调函数为 null
catch(fn) {
return this.then(null, fn)
}
}
MyPromise.resolve = function (value) {
return new MyPromise((resolve, rejected) => resolve(value))
}
MyPromise.reject = function (reason) {
return new MyPromise((resolve, rejected) => rejected(reason))
}
MyPromise.all = function (promiseList = []) {
return new MyPromise((resolve, rejected) => {
const result = [] // 储存所有的 Promise
const length = promiseList.length
let resolvedCount = 0
promiseList.forEach((p) => {
p.then((data) => {
result.push(data)
if (++resolvedCount === length) {
resolve(result)
}
}).catch((err) => {
rejected(err)
})
})
})
}
MyPromise.race = function (promiseList = []) {
let resolved = false
return new MyPromise((resolve, reject) => {
promiseList.forEach((p) => {
p.then((data) => {
if (!resolved) {
resolve(data)
resolved = true
}
}).catch((err) => {
reject(err)
})
})
})
}