手写 Promise
本篇假设你已经了解了 Promise 如何使用,如果你对 Promise 还不是很了解,建议先阅读一下 MDN或者是 ES6 入门教程
Promise 规范
要想实现一个 Promise,那首先得清楚 Promise 的定义才行。让我们来看一下 Promise/A+规范,如果你不想看也没关系,可以直接看下面俺的总结(翻译 🤫)。
术语
- Promise 是一个符合 Promise 规范且定义了 then 方法的对象或函数
- thenable 是定义了 then 方法的对象或函数
- value 是任意合法的值,包括 undefined、thenable 或者是 promise
- exception 是用 throw 关键字抛出去的值,也称作异常
- reason 是标示 Promise 被 rejected 的理由
Promise 状态
一个 Promise 必须为三种状态的其中之一:pending、fulfilled、rejected
pending 状态的 Promise 可以转换为 fulfilled 或是 rejected 状态
fulfilled 状态的 Promise 无法再更改状态,且必须包含一个 value
rejected 状态的 Promise 无法再更改状态,且必须包含一个 reason
then 方法
Promise 必须提供一个 then 方法来访问当前或者是最终的 value 或者是 reason
then 方法接收两个可选的函数作为参数,如果任一参数不为函数,那么此参数将被忽略
promise.then(onFulfilled, onRejected)
- onFulfilled 只能且必须在 Promise 更改为 fulfilled 状态时被调用,value 值将作为第一个参数,且只能调用一次
- onRejected 只能且必须在 Promise 更改为 rejected 状态时被调用,reason 将作为第一个参数,且只能调用一次
- onFulfilled 和 onRejected 只能在异步队列中被调用
then 方法允许在同个 Promise 中被多次调用,如下面代码
let p = new Promise((resolve, rejected) => {
resolve()
})
p.then((res) => {})
p.then((res) => {})
p.then((res) => {})
then 方法必须返回一个 Promise
let promise2 = promise1.then(onFulfilled, onRejected)
- 无论 onFulfilled 或者是 onRejected return 出来一个值 x,都得先执行一遍 Promise Resolution Procedure
[[Resolve]](promise2, x)
- 无论 onFulfilled 或者是 onRejected 抛出一个异常 e,Promise2 都将以用这个 e 作为 reason 被 rejected
- 如果 onFulfilled 不是一个函数,但是 promise1 更改为了 fulfilled 状态,那么 promise2 也将以同样的 value 更改为 fulfilled 状态
- 如果 onRejected 不是一个函数,但是 promise1 更改为了 rejected 状态,那么 promise2 也将以同样的 reason 更改为 rejected 状态
- 后面这两条翻译成大白话就是,如果 Promise 更改了状态,没有被相应的处理的话,就会将这个状态以及 value 或者是 reason 传递下去
Promise Resolution Procedure
let promise2 = promise1.then(onFulfilled, onRejected)
如果我们将第一个 then 返回的值,记做 x,先判断 x 是不是 Promise
如果 x 是 Promise,则取 Promise 返回的结果作为 promise2 成功的结果
如果是普通值,则直接将这个值作为 promise2 的结果
我们需要一个函数,用来处理 promise2 和 x 的关系
上手
让我们一点点来实现自己的 Promise,就叫它 MyPromise 吧。根据规范 MyPromise 有 state、value、reason、以及可能抛出去的异常 exception,exception 就用 try catch 来捕获,上代码
class MyPromise {
constructor(executor) {
this.state = "pending"
this.value = undefined
this.reason = undefined
let resolve = (value) => {
this.state = "fulfilled"
this.value = value
}
let reject = (e) => {
this.state = "rejected"
this.reason = e
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled" && onFulfilled instanceof Function) {
onFulfilled(this.value)
} else if (this.state === "rejected" && onRejected instanceof Function) {
onRejected(this.reason)
}
}
}
MyPromise 的构造函数接收一个函数,我们称为 executor。
executor 提供了两个方法 resolve 和 reject,分别可以是 Promise 更改为 fulfilled 和 rejected 状态,执行过程中出现任何错误也可以 reject。
在 23 到 29 行,我们在原型上挂载了 then 方法,then 方法接收两个函数作为参数,分别在 fulfilled 和 rejected 的时候被调用,那让我们试试这个 MyPromise 效果咋样
let p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then(
(res) => {
console.log(res)
},
(e) => {
console.error(e)
}
)
第 7 行确实能将 resolve 的 value 打印出来呢,🐂🍺,当然在 executor 中 reject 话,肯定也是能在第 10 行打印的啦。
当然这只是实现了部分规范而已,还得继续完善
让我们看看规范中还说了啥,onFulfilled 和 onRejected 必须异步调用,那我们用 setTimeout 给裹起来
then(onFulfilled, onRejected){
setTimeout(()=> {
if (this.state === "fulfilled" && onFulfilled instanceof Function) {
onFulfilled(this.value)
} else if (this.state === "rejected" && onRejected instanceof Function) {
onRejected(this.reason)
}
})
}
更改完之后就符合事件循环机制了,注意还是有点不对劲,稍后再提
new MyPromise((resolve, reject) => {
resolve(1)
}).then((res) => {
console.log(res)
})
console.log("end")
// 打印顺序,end => 1
解决异步 resolve
上面的例子中,在 executor 中执行 resolve 的时候,我们是直接 resolve。然而在实际使用场景中,我们可能在异步中 resolve
new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000)
}).then((res) => {
console.log(res)
})
实际上并没有打印出来,因为执行 then 的时候 MyPromise 实例的 state 还是 pending。等 1s 后,MyPromise 实例 fulfilled 的时候,then 方法也没有再执行
那,我们把 onFulfilled 和 onRejected 都存起来,等 resolve 或者是 reject 的时候再依次执行,说干就干,下面只展示了更改部分的代码
class MyPromise {
constructor(executor) {
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
this.state = "fulfilled"
this.value = value
this.onFulfilledCallbacks.forEach((fn) => {
fn(this.value)
})
}
const reject = (e) => {
this.state = "rejected"
this.reason = e
this.onFulfilledCallbacks.forEach((fn) => {
fn(this.reason)
})
}
}
then(onFulfilled, onRejected) {
switch (this.state) {
case "pending":
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
break
}
}
}
靠谱,收工,再看下一条需求
then 方法 return Promise
我们在使用 Promise 的时候,可以链式使用,比如在 then 之后接上 then 或者 catch,之所以能够这样做,那是因为在 then 方法使用之后,同时又返回了一个 Promise
回顾 Promise Resolution 部分的文档,我们暂且将这个函数叫做 resolvePromise 吧,调整一下 then 方法
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.state === "fulfilled") {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.state === "rejected") {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.state === "pending") {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
// 返回promise,完成链式
return promise2
}
接下来我们就要实现 resolvePromise 方法了
来看一下 resolvePromise 的需求,具体可查阅文档Promise/A+规范,下文仅做概括
- 如果 promise2 和 x 值向相同的地址,则用
TypeError
作为 reason reject - 如果 x 是个 promise
- 状态为 pending,则需要等待,直到 fulfilled 或者 rejected 为止
- 状态为 fulfilled,则用与 promise2 相同的 value fulfill
- 状态为 rejected,则用与 promise2 相同的 reason reject
- 如果 x 是个 Object 或者 Function
- 如果 x 是个普通的值,则直接 resolve
我们先简单点,直接假定 x 为数值
function resolvePromise(promise2, x, resolve, reject) {
if (x === Promise2) return reject(new TypeError("xxx"))
resolve(x)
}