目录
- resolve与reject的调用时机
- 封装优化
- 回调返回Promise
- isPromise
- 手动调用then
- 微队列
- catch
- resolve
- reject
- all
- 传入的序列为空
- 传入的值非Promise
- race
- 完整的Promise代码
如果没有看过上半部分的铁铁可以看看这篇文章
js手写Promise(上)
resolve与reject的调用时机
在Promise的then中,我们需要解决两个问题,一个是onFulfilled和onRejected什么时候调用,一个是resolve和reject什么时候调用,第一个问题我们在上篇文章中解决了,现在我们需要解决第二个问题
具体而言,什么时候调用resolve和reject分为两种情况
-
传入的参数
不是函数
因为我们已经提前将resolve和reject传递到了handlers中,所以我们可以在run中处理相关逻辑#run() {if (this.#state === MyPromise.#PENDING) returnwhile (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {if (typeof handler.onFulfilled !== "function") {handler.resolve(this.#value)} else {handler.onFulfilled(this.#value)}}else if (this.#state === MyPromise.#REJECTED) {if (typeof handler.onRejected !== "function") {handler.reject(this.#value)} else {handler.onRejected(this.#value)}}} }如果传入的参数不是
函数的话,那then返回的Promise就穿透了,状态与调用then的Promise实例状态一致,如果调用then的实例状态为fulfilled则then返回的实例就调用resolve,反之亦然 -
传入的参数是
函数
如果传入的参数是函数,我们就需要判断在执行函数的时候有没有报错,如果没有就代表函数执行成功,调用resolve,否则调用reject#run() { if (this.#state === MyPromise.#PENDING) return while (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {if (typeof handler.onFulfilled !== "function") {handler.resolve(this.#value)} else {try {const data = handler.onFulfilled(this.#value)handler.resolve(data)} catch (error) {handler.reject(error)}}}else if (this.#state === MyPromise.#REJECTED) {if (typeof handler.onRejected !== "function") {handler.reject(this.#value)} else {try {const data = handler.onRejected(this.#value)handler.resolve(data)} catch (error) {handler.reject(error)}}} } }
封装优化
这样问题就解决了,但是我们发现函数中有许多重复代码,我们可以将这些代码封装成一个函数
#run() {if (this.#state === MyPromise.#PENDING) returnwhile (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)}else if (this.#state === MyPromise.#REJECTED) {this.#runOne(handler.onRejected, handler.resolve, handler.reject)}}
}
#runOne(callback, resolve, reject) {if (typeof callback !== "function") {const settled = this.#state === MyPromise.#FULFILLED ? resolve : rejectsettled(this.#value)} else {try {const data = callback(this.#value)resolve(data)} catch (error) {reject(error)}}
}
回调返回Promise
这种情况比较特殊,如果返回的结果是一个Promise的话调用resolve还是reject由这个新的Promise实例决定,我们只需要手动调用它的then方法
isPromise
在调用它的then方法之前我们还需要判断返回的结果是不是一个Promise,PromiseA+规范规定,对象或是函数,如果存在then方法就是Promise,所以我们可以写出这么一个辅助函数
class MyPromise {static #isPromise(promise) {if (typeof promise === "function" || typeof promise === "object") {if (typeof promise.then === "function") return true}return false}
}
手动调用then
现在我们就可以手动调用then方法了
#runOne(callback, resolve, reject) {if (typeof callback !== "function") {const settled = this.#state === MyPromise.#FULFILLED ? resolve : rejectsettled(this.#value)} else {try {const data = callback(this.#value)if (MyPromise.#isPromise(data)) data.then(resolve, reject)else resolve(data)} catch (error) {reject(error)}}
}
微队列
至此我们的then方式实现的差不多了,还有一个小细节就是,then方法里的任务是放入微任务队列里执行的,所以我们还需要封装一个函数用于将任务放入微任务队列
#runOne(callback, resolve, reject) {MyPromise.#mircoTask(() => {if (typeof callback !== "function") {const settled = this.#state === MyPromise.#FULFILLED ? resolve : rejectsettled(this.#value)} else {try {const data = callback(this.#value)if (MyPromise.#isPromise(data)) data.then(resolve, reject)else resolve(data)} catch (error) {reject(error)}}})
}
static #mircoTask(callback) {if (typeof window === "object" && MutationObserver) {const observer = new MutationObserver(callback)const p = document.createElement('p')observer.observe(p, { childList: true })p.innerText = '1'} else if (typeof global === "object" && process) {process.nextTick(callback)} else {setTimeout(callback, 0)}
}
如果想要把一个任务放入微任务队列需要根据环境分类处理,具体来说有以下几种情况
- node环境
node环境里有一个api叫process,process.nextTick能将一个任务放入微任务队列中 - 浏览器环境
浏览器中有一个观察器叫MutationObserver,它用于观察一个元素是否变化,如果变化了就将预先设定好的函数放入微任务队列中 - 其他环境
如果宿主环境既不是node也不是浏览器,或者不支持MutationObserver和process,那么就只能通过setTimeOut来模拟微队列了
最后我们来测试一下我们的then方法
let p1 = new MyPromise((resolve, reject) => {reject(123)
})
p1.then((res) => {console.log("1resolve" + res)
}, (err) => {console.log("1reject" + err)
})
p1.then((res) => {console.log("2resolve" + res)
}, (err) => {return new MyPromise((resolve, reject) => {resolve(err)})
}).then((res) => {console.log("3resolve" + res)
}, (err) => {console.log("3reject" + err)
})

catch
catch的实现与then类似,都是向handlers里放入回调,只不过catch放入的回调中onFulfilled为undefined
class MyPromise {catch(onRejected) {return new MyPromise((resolve, reject) => {this.#handlersPush(undefined, onRejected, resolve, reject)this.#run()})}
}
我们来测试一下
let p1 = new MyPromise((resolve, reject) => {reject(123)
})
p1.catch((err) => {console.log(err)
})
let p2 = new MyPromise((resolve, reject) => {reject(456)
})
p2.then(null, (err) => {return new MyPromise((resolve, reject) => {reject(789)})
}).catch(err => {console.log(err)
})

resolve
这里我们要实现的resolve是Promise的类方法,回忆我们之前使用Promise的经验,如果resolve中传递的不是Promise,那么Promise会将其包装成一个Promise返回,如果传入的是一个Promise,那么会调用它的then方法,知道了这些我们的代码就可以这么写
static resolve(value) {return new MyPromise((resolve, reject) => {if (MyPromise.#isPromise(value)) value.then(resolve, reject)else resolve(value)})
}
我们来测试一下
MyPromise.resolve(1).then(console.log)
MyPromise.resolve(new MyPromise((resolve, reject) => {setTimeout(() => {resolve(2)}, 1000)
})).then(console.log)

reject
reject和resolve类似,只不过resolve是调用resolve方法,而reject是调用reject方法
static reject(reason) {return new MyPromise((resolve, reject) => {if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)else reject(reason)})
}
因为和resolve类似,所以我就不测试了
all
all也是Promise的一个类方法,我们需要向all里传入一个参数,表示为一系列Promise的序列,可以是set,也可以是数组,all也是返回一个Promise,知道了这些后我们就能将all的声明写出来
static all(promises) {return new MyPromise((resolve, reject) => {})
}
那么现在的问题就是我们什么时候调用resolve和reject
传入的序列为空
如果传入的序列为空就没什么好说的了,直接resolve([])就行,那怎么判断序列是否为空呢,定义一个长度变量,我们可以通过for...of来遍历序列,每遍历一次长度变量自增,遍历完后如果长度变量依旧为0表示序列为空
static all(promises) {return new MyPromise((resolve, reject) => {let i = 0for (const item of promises) {i++}if (i === 0) resolve([])})
}
传入的值非Promise
因为我们并不确定拿到的东西是否是一个Promise,所以我们需要使用Promise.resolve将它包裹起来
static all(promises) {return new MyPromise((resolve, reject) => {let i = 0for (const item of promises) {i++MyPromise.resolve(item).then()}if (i === 0) resolve([])})
}
根据Promise中all的逻辑,如果序列中有一个失败,那all返回的Promise的状态就是失败
如果当前Promise的结果为成功的话则需要做两件事,一件事汇总结果,一件事判断Promise是否全部完成
因为all要求返回的结果与传递的序列顺序要求一致,所以在汇总结果时不能使用push,而是应该使用下标
我们每完成一个Promise就记一次数,只要这个数字和我们之前统计的长度变量相同,就代表着这一串Promise执行结束,可以resolve了
所以我们的代码可以写成这个样子
static all(promises) {return new MyPromise((resolve, reject) => {let i = 0let result = []let fulfilled = 0for (const item of promises) {let index = ii++MyPromise.resolve(item).then((data) => {result[index] = datafulfilled++if (fulfilled === i) resolve(result)}, reject)}if (i === 0) resolve([])})
}
我们来测试一下
let p1 = new MyPromise((resolve, reject) => {resolve(1)
})
let p2 = new MyPromise((resolve, reject) => {reject(2)
})
MyPromise.all([]).then(console.log)
MyPromise.all([p1, p2]).then(console.log).catch(err => {console.log("err" + err)
})
MyPromise.all([p1, 2, 3, 4]).then(console.log)

race
race和all都是Promise的类方法,实现思路也是大同小异,all是等待全部Promise的结果,race是只要有一个Promise有结果就行,代码如下
static race(promises) {return new MyPromise((resolve, reject) => {let i = 0for (const item of promises) {i++Promise.resolve(item).then(resolve, reject)}if (i === 0) resolve([])})
}
完整的Promise代码
class MyPromise {#state = "pending"#value = nullstatic #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)}}then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {this.#handlersPush(onFulfilled, onRejected, resolve, reject)this.#run()})}catch(onRejected) {return new MyPromise((resolve, reject) => {this.#handlersPush(undefined, onRejected, resolve, reject)this.#run()})}static resolve(value) {return new MyPromise((resolve, reject) => {if (MyPromise.#isPromise(value)) value.then(resolve, reject)else resolve(value)})}static reject(reason) {return new MyPromise((resolve, reject) => {if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)else reject(reason)})}static all(promises) {return new MyPromise((resolve, reject) => {let i = 0let result = []let fulfilled = 0for (const item of promises) {let index = ii++MyPromise.resolve(item).then((data) => {result[index] = datafulfilled++if (fulfilled === i) resolve(result)}, reject)}if (i === 0) resolve([])})}static race(promises) {return new MyPromise((resolve, reject) => {let i = 0for (const item of promises) {i++Promise.resolve(item).then(resolve, reject)}if (i === 0) resolve([])})}static #mircoTask(callback) {if (typeof window === "object" && MutationObserver) {const observer = new MutationObserver(callback)const p = document.createElement('p')observer.observe(p, { childList: true })p.innerText = '1'} else if (typeof global === "object" && process) {process.nextTick(callback)} else {setTimeout(callback, 0)}}static #isPromise(promise) {if (typeof promise === "function" || typeof promise === "object") {if (typeof promise.then === "function") return true}return false}#changeState(state, value) {if (this.#state !== MyPromise.#PENDING) returnthis.#state = statethis.#value = valuethis.#run()}#handlersPush(onFulfilled, onRejected, resolve, reject) {this.#handlers.push({onFulfilled,onRejected,resolve,reject})}#run() {if (this.#state === MyPromise.#PENDING) returnwhile (this.#handlers.length > 0) {const handler = this.#handlers.shift()if (this.#state === MyPromise.#FULFILLED) {this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)}else if (this.#state === MyPromise.#REJECTED) {this.#runOne(handler.onRejected, handler.resolve, handler.reject)}}}#runOne(callback, resolve, reject) {MyPromise.#mircoTask(() => {if (typeof callback !== "function") {const settled = this.#state === MyPromise.#FULFILLED ? resolve : rejectsettled(this.#value)} else {try {const data = callback(this.#value)if (MyPromise.#isPromise(data)) data.then(resolve, reject)else resolve(data)} catch (error) {reject(error)}}})}
}