赞
踩
如果没有看过上半部分的铁铁可以看看这篇文章
js手写Promise(上)
在Promise
的then
中,我们需要解决两个问题,一个是onFulfilled和onRejected什么时候调用
,一个是resolve和reject什么时候调用
,第一个问题我们在上篇文章中解决了,现在我们需要解决第二个问题
具体而言,什么时候调用resolve
和reject
分为两种情况
传入的参数不是函数
因为我们已经提前将resolve
和reject
传递到了handlers
中,所以我们可以在run
中处理相关逻辑
#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 { 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) return while (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 : reject settled(this.#value) } else { try { const data = callback(this.#value) resolve(data) } catch (error) { reject(error) } } }
这种情况比较特殊,如果返回的结果是一个Promise
的话调用resolve
还是reject
由这个新的Promise实例
决定,我们只需要手动调用它的then
方法
在调用它的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
方法了
#runOne(callback, resolve, reject) {
if (typeof callback !== "function") {
const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
settled(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 : reject settled(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
环境里有一个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
的实现与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
是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
和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
也是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 = 0
for (const item of promises) {
i++
}
if (i === 0) resolve([])
})
}
因为我们并不确定拿到的东西是否是一个Promise
,所以我们需要使用Promise.resolve
将它包裹起来
static all(promises) {
return new MyPromise((resolve, reject) => {
let i = 0
for (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 = 0 let result = [] let fulfilled = 0 for (const item of promises) { let index = i i++ MyPromise.resolve(item).then((data) => { result[index] = data fulfilled++ 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
和all
都是Promise
的类方法,实现思路也是大同小异,all
是等待全部Promise
的结果,race
是只要有一个Promise
有结果就行,代码如下
static race(promises) {
return new MyPromise((resolve, reject) => {
let i = 0
for (const item of promises) {
i++
Promise.resolve(item).then(resolve, reject)
}
if (i === 0) resolve([])
})
}
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) } } 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 = 0 let result = [] let fulfilled = 0 for (const item of promises) { let index = i i++ MyPromise.resolve(item).then((data) => { result[index] = data fulfilled++ if (fulfilled === i) resolve(result) }, reject) } if (i === 0) resolve([]) }) } static race(promises) { return new MyPromise((resolve, reject) => { let i = 0 for (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) return this.#state = state this.#value = value this.#run() } #handlersPush(onFulfilled, onRejected, resolve, reject) { this.#handlers.push({ onFulfilled, onRejected, resolve, reject }) } #run() { if (this.#state === MyPromise.#PENDING) return while (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 : reject settled(this.#value) } else { try { const data = callback(this.#value) if (MyPromise.#isPromise(data)) data.then(resolve, reject) else resolve(data) } catch (error) { reject(error) } } }) } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。