如果是不知道或者对Promise
不熟悉的铁铁可以先看我这篇文章
Promise
在最开始,我们先不去考虑Promise
内部是怎么实现,而是先将自己的Promise
声明出来,这里我使用ES6
的class
来声明
class MyPromise {
}
在我们new
一个Promise
的时候会传入一个回调函数
,这个回调函数有两个形参
,一个resolve
,一个reject
,这个函数将交给Promise
来立即执行
,所以我们的constructor
可以这么写
class MyPromise {
constructor(func) {
func(resolve, reject)
}
}
值得注意的是,Promise
本身就是一个任务
,而回调函数
表示的是任务的执行过程
,所以constructor
中的形参
应该叫executor
而不是func
class MyPromise {
constructor(executor) {
executor(resolve, reject)
}
}
resolve
和reject
也是函数,那么这两个函数定义在哪呢,有2种方案
定义在constructor
中
constructor(executor) {
const resolve = (data) => {
}
const reject = (reason) => {
}
executor(resolve, reject)
}
将其变为原型方法
class MyPromise {
constructor(executor) {
func(this.#resolve, this.#reject)
}
#reject(reason) { }
#resolve(data) { }
}
因为这个函数我们只会在类的内部使用,并不希望用户能在外部访问,所以我们将它定义为私有成员
只不过这么写的话会有this
的指向问题,我们需要使用强制绑定
来将函数绑定到正确的地方
class MyPromise {
constructor(executor) {
func(this.#resolve.call(this), this.#reject.call(this))
}
#reject(reason) { }
#resolve(data) { }
}
这里我选择第一种方法
现在我们声明了resolve
与reject
两个函数,但具体这两个函数做什么我们并不清楚,事实上这两个函数做的都是同一件事,改变当前Promise实例的状态与值
,只不过resolve
是将当前实例的状态改为fulfilled
,而reject
是将当前实例的状态改为rejected
,明白了这一点我们就能写出如下代码
class MyPromise {
#state = "pending"
#value = null
constructor(executor) {
const resolve = (data) => {
this.#state = "fulfilled"
this.#value = data
}
const reject = (reason) => {
this.#state = "rejected"
this.#value = reason
}
executor(resolve, reject)
}
}
我们声明了两个私有属性
,无论是state
还是value
我们都不希望用户能从外部访问,state
用于记录当前实例的状态
,而value
用于记录当前实例得到的值
这么写就完了吗?当然没有,在Promise
中状态一旦确定就不能再更改
,反映到代码层面就是无论是在回调函数中写多少个resolve
和reject
,Promise
都只会执行第一个,而我们的Promise
中目前并没有实现这个功能
const resolve = (data) => {
if (this.#state !== "pending") return
this.#state = "fulfilled"
this.#value = data
}
const reject = (reason) => {
if (this.#state !== "pending") return
this.#state = "rejected"
this.#value = reason
}
我们在resolve
和reject上
都加了一行判断,如果当前实例的state
不是pending
的话就说明状态已经改变,不能再继续执行
写到这里我们发现resolve
和reject
函数中的重复代码有点多,所以我们可以将其封装成一个独立的函数
class MyPromise {
#state = "pending"
#value = null
constructor(executor) {
const resolve = (data) => {
this.#changeState("fulfilled", data)
}
const reject = (reason) => {
this.#changeState("rejected", reason)
}
executor(resolve, reject)
}
#changeState(state, value) {
if (this.#state !== "pending") return
this.#state = state
this.#value = value
}
}
现在我们发现在我们的代码中还存在着一些硬编码
的部分,如状态不应该直接使用字符串而是需要使用变量存起来,这样如果以后状态的名称发生改变,我们也就只需要更改变量的内容
class MyPromise {
#state = "pending"
#value = null
static #PENDING = "pending"
static #FULFILLED = "fulfilled"
static #REJECTED = "rejected"
constructor(executor) {
const resolve = (data) => {
this.#changeState(MyPromise.#FULFILLED, data)
}
const reject = (reason) => {
this.#changeState(MyPromise.#REJECTED, reason)
}
executor(resolve, reject)
}
#changeState(state, value) {
if (this.#state !== MyPromise.#PENDING) return
this.#state = state
this.#value = value
}
}
我们将三种状态用变量存起来,因为三个状态只会在内部使用而且每个实例都会拥有这三个状态,所以我将其定义为静态私有成员
现在大部分问题我们都解决了,但是在回调函数中抛出错误
的情况我们并没有处理,在Promise
中如果回调函数中抛出了错误会被Promise内部捕获到
,直接reject
,那么我们的代码就可以这么写
constructor(executor) {
const resolve = (data) => {
this.#changeState(MyPromise.#FULFILLED, data)
}
const reject = (reason) => {
this.#changeState(MyPromise.#REJECTED, reason)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
至此我们就将MyPromise
的构造器
部分完成了
在PromiseA+
规范中通篇都在说什么是Promise
,简单地说就是Promise
可以是一个对象
或者是函数
,但无论是什么都必须要有then
方法,如果有then
方法那就是Promise
所以then
方法是Promise
中的核心,同时也是手写Promise
中最难的一部分,如果能将then
方法手写出来那整个Promise
就可以算是大部分完成了
我们回忆一下Promise
中的then
方法,发现then
方法会传入两个参数,一个是成功时的回调函数
,一个是失败时的回调函数
,那我们可以这么定义
class MyPromise {
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
})
}
}
因为then
方法是每个实例都拥有并且用到的,所以我们将其定义为成员方法
,为了实现Promise的链式调用
所以then
方法必须返回一个Promise
,那么在这个返回的Promise
中,我们究竟该做些什么呢
onFulfilled
和onRejected
什么时候调用,这个问题很好解决,依据当前Promise
的状态判断是调用onFulfilled
还是onRejected
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.#state === MyPromise.#FULFILLED) onFulfilled(this.#value)
if (this.#state === MyPromise.#REJECTED) onRejected(this.#value)
})
}
这么写似乎并没有什么问题,那我们来测试一下
let p1 = new MyPromise((resolve, reject) => {
resolve(123)
})
let p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(456)
}, 1000)
})
p1.then(data => {
console.log(data)
})
p2.then(data => {
console.log(data)
})
看得出来,p1
成功运行了,但p2
似乎有点问题,因为p2
在运行到then
的时候p2
的状态还是pending
,p2
的状态会在一秒钟后才改变,但then
方法早在这之前就调用了,所以为了避免这种情况,我们需要在状态改变的时候再次调用then方法
再次调用then方法
说起来并不精确,我们其实真正想要的并不是调用then
方法,而是想要在状态改变的时候调用onFulfiled
或者onRejected
,那么第一个问题就来了,我们在哪里能知道状态什么时候被改变了?答案是changeState
changeState
是用来改变当前实例的状态的函数,当它第一次运行时状态肯定被改变,我们只需要在这里调用onFulfilled
或者onRejected
,但是有一个新问题,这两个回调函数都是直接传入then
中的,我们无法在changeState中拿到这两个函数
,那该怎么办呢?我们可以用一个中间变量存储
class MyPromise {
#handler = {}
#changeState(state, value) {
if (this.#state !== MyPromise.#PENDING) return
this.#state = state
this.#value = value
if (this.#state === MyPromise.#FULFILLED) this.#handler.onFulfilled(this.#value)
else if (this.#state === MyPromise.#REJECTED) this.#handler.onRejected(this.#value)
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.#state === MyPromise.#FULFILLED) onFulfilled(this.#value)
else if (this.#state === MyPromise.#REJECTED) onRejected(this.#value)
else this.#handler = {
onFulfilled,
onRejected,
resolve,
reject
}
})
}
}
这样问题就解决了,但这里面的重复代码有点多,我们可以将其封装成一个函数
class MyPromise {
#changeState(state, value) {
if (this.#state !== MyPromise.#PENDING) return
this.#state = state
this.#value = value
this.#run()
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handler = {
onFulfilled,
onRejected,
resolve,
reject
}
this.#run()
})
}
#run() {
if (this.#state === MyPromise.#FULFILLED) {
this.#handler.onFulfilled(this.#value)
}
else if (this.#state === MyPromise.#REJECTED) {
this.#handler.onRejected(this.#value)
}
}
}
我们封装了一个run
函数,这个函数专门用来执行then
的回调,我们还是用上面那个代码测试
至此异步then
问题解决
有时我们会在一个实例上多次调用then
方法,在实例的状态改变后这些then
方法的回调函数应该继续执行,但我们的代码却并没有实现
多个then
就意味着handler
不是一个对象
而是一个数组
,run
方法也不再调用一个handler
,而是遍历handlers
,将对应状态的回调函数全都取出来执行
class MyPromise {
#handlers = []
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlersPush(onFulfilled, onRejected, resolve, reject)
this.#run()
})
}
#run() {
if (this.#state === MyPromise.#PENDING) return
while (this.#handlers.length > 0) {
const handler = this.#handlers.shift()
if (this.#state === MyPromise.#FULFILLED) {
handler.onFulfilled(this.#value)
}
else if (this.#state === MyPromise.#REJECTED) {
handler.onRejected(this.#value)
}
}
}
#handlersPush(onFulfilled, onRejected, resolve, reject) {
this.#handlers.push({
onFulfilled,
onRejected,
resolve,
reject
})
}
}
我们封装了一个辅助函数用于向handlers
放入回调,在run
中我们会一直在handlers
里取出回调执行,我们使用以下代码测试
let p1 = new MyPromise((resolve, reject) => {
resolve(123)
})
p1.then(data => {
console.log("第一个then" + data)
})
p1.then(data => {
console.log("第二个then" + data)
})
class MyPromise {
#state = "pending"
#value = null
static #PENDING = "pending"
static #FULFILLED = "fulfilled"
static #REJECTED = "rejected"
#handlers = []
constructor(executor) {
const resolve = (data) => {
this.#changeState(MyPromise.#FULFILLED, data)
}
const reject = (reason) => {
this.#changeState(MyPromise.#REJECTED, reason)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
#changeState(state, value) {
if (this.#state !== MyPromise.#PENDING) return
this.#state = state
this.#value = value
this.#run()
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlersPush(onFulfilled, onRejected, resolve, reject)
this.#run()
})
}
#run() {
if (this.#state === MyPromise.#PENDING) return
while (this.#handlers.length > 0) {
const handler = this.#handlers.shift()
if (this.#state === MyPromise.#FULFILLED) {
handler.onFulfilled(this.#value)
}
else if (this.#state === MyPromise.#REJECTED) {
handler.onRejected(this.#value)
}
}
}
#handlersPush(onFulfilled, onRejected, resolve, reject) {
this.#handlers.push({
onFulfilled,
onRejected,
resolve,
reject
})
}
}
因为内容过多,所以我将文章分为两篇,接下来的部分请看我的这篇文章
js手写Promise(下)