手写Promise

Promise规范

https://promisesaplus.com/ PromiseA+规范

A+规范只有then方法

Promise 类设计

class MyPromise {}
function MyPromise() {}

两种方式,我选第一种

构造函数constructor设计

实现:

  1. 让executor函数执行起来
  2. 定义状态常量
  3. 定义resolve,reject回调,执行的时候改变状态
const STATUE_PENDING = 'pending'
const STATUE_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'

class MyPromise {
constructor(executor) {
// 保存状态与值
this.status = STATUE_PENDING
// 由于后面的then方法可能会用到这些参数值,需要保存起来
this.value = undefined
this.reason = undefined

// 定义resolve回调
const resolve = (value) => {
// 改变状态并保存值
this.status = STATUE_FULFILLED
this.value = value
console.log("resolve执行", value);
}
// 定义reject回调
const reject = (reason) => {
// 改变状态并保存值
this.status = STATUS_REJECTED
this.reason = reason
console.log("reject执行", reason);
}

executor(resolve, reject)
}
}

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
resolve("111")
reject("222")
})
  1. 为了不让resolve,reject回调可以同时执行,我们需要加一个判断,只有状态为pending的时候才执行回调
const resolve = (value) => {
if (this.status === STATUE_PENDING) { // 加一个判断
this.status = STATUE_FULFILLED
this.value = value
console.log("resolve执行", value);
}
}

const reject = (reason) => {
if (this.status === STATUE_PENDING) { // 加一个判断
this.status = STATUS_REJECTED
this.reason = reason
console.log("reject执行", reason);
}
}

then 方法的实现

简单实现

  1. then方法接收两个参数,分别是成功时的回调onFulfilled,失败时的回调onRejected
  2. 把回调保存到当前的promise实例,并在对应的resolve,reject回调时执行
class MyPromise {
constructor(executor) {
this.status = STATUE_PENDING
this.value = undefined
this.reason = undefined

const resolve = (value) => {
if (this.status === STATUE_PENDING) {
this.status = STATUE_FULFILLED
this.value = value
console.log("resolve执行", value);
this.onFulfilled(this.value) // 在这里执行
}
}

const reject = (reason) => {
if (this.status === STATUE_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
console.log("reject执行", reason);
this.onRejected(this.reason) // 在这里执行
}
}

executor(resolve, reject)
}

then(onFulfilled, onRejected) {
this.onFulfilled = onFulfilled // 保存方法
this.onRejected = onRejected // 保存方法
}
}

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
resolve("111")
reject("222")
})

promise.then(res => {
console.log("res: ", res);
}, err => {
console.log("err: ", err);
})

这时候我们会发现,报错了:this.onFulfilled is not a function

为什么??

我们捋一捋代码的执行过程就可以发现,当执行resolve("111") 的时候,都还没有到then那一步,也就是说还没执行then方法,那么怎么可能会有 onFulfilled, onRejected 方法呢!

这时候我们就用到了一个方法:queueMicrotask()

MDN:https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask

简单来说,这个方法可以把我们传入的方法加入到微任务队列,让它延迟到当前主线程的任务执行完之后再执行

所以可以在resolve方法执行的时候,先不执行onFulfilled,把它加入到微任务队列,接着就会继续执行下面的then方法,这时候就有onFulfilled, onRejected 方法了,最后主线程任务执行完,再执行微任务队列中的 onFulfilled,,(reject同理)

const resolve = (value) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => { // 加入到微任务队列
this.status = STATUE_FULFILLED
this.value = value
this.onFulfilled(this.value)
})
}
}

const reject = (reason) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => { // 加入到微任务队列
this.status = STATUS_REJECTED
this.reason = reason
this.onRejected(this.reason)
})
}
}

但是这个时候又出现了一个问题,resolve和reject可以同时调用了

因为我们执行resolve的实现,加入到微任务队列,因此状态没有被改变,依然是pending

接着执行reject,判断状态是pending,所以也加入到微任务队列了

解决:在执行微任务的代码一开始加入一个判断,如果状态已经不是pending了,直接return出去,停止执行

const resolve = (value) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => {
if (this.status !== STATUE_PENDING) return // 添加判断
this.status = STATUE_FULFILLED
this.value = value
this.onFulfilled(this.value)
})
}
}

const reject = (reason) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => {
if (this.status !== STATUE_PENDING) return // 添加判断
this.status = STATUS_REJECTED
this.reason = reason
this.onRejected(this.reason)
})
}
}

当然,有人可能会说把状态改变放到微任务外面不就行了吗?

不行!!这样在后面实现链式调用会出现问题

你后面可以试一下,多个then执行和链式调用一起的时候,执行顺序是有问题的

至于为什么,那就自己捋一捋整个代码的执行顺序叭

同时调用多个then的实现

上面的实现是不能同时调用多个then的

promise.then(res => {
console.log("res: ", res);
}, err => {
console.log("err: ", err);
})

promise.then(res => {
console.log("res2: ", res);
}, err => {
console.log("err2: ", err);
})

后面的then会覆盖前面的,因此只会执行 res2 或者 err2

实现思路:我们需要把then传入的回调保存到一个数组,到时候一起执行

class MyPromise {
constructor(executor) {
this.status = STATUE_PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = [] // 定义数组存放成功的回调
this.onRejectedCallbacks = [] // 定义数组存放失败的回调

const resolve = (value) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => { // 加入到微任务队列
if (this.status !== STATUE_PENDING) return
this.status = STATUE_FULFILLED
this.value = value
// 用forEach遍历执行数组中的回调
this.onFulfilledCallbacks.forEach(fn => {
fn(this.value)
})
})
}
}

const reject = (reason) => {
if (this.status === STATUE_PENDING) {
queueMicrotask(() => { // 加入到微任务队列
if (this.status !== STATUE_PENDING) return
this.status = STATUS_REJECTED
this.reason = reason
// 用forEach遍历执行数组中的回调
this.onRejectedCallbacks.forEach(fn => {
fn(this.reason)
})
})
}
}

executor(resolve, reject)
}

then(onFulfilled, onRejected) {
this.onFulfilledCallbacks.push(onFulfilled) // 保存到数组
this.onRejectedCallbacks.push(onRejected)
}
}

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
resolve("111")
reject("222")
})

promise.then(res => {
console.log("res: ", res);
}, err => {
console.log("err: ", err);
})

promise.then(res => {
console.log("res2: ", res);
}, err => {
console.log("err2: ", err);
})

延迟调用then

还有一种情况:resolve或reject执行完之后,延迟执行then

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
resolve("111")
// reject("222")
})

setTimeout(() => {
promise.then(res => {
console.log("res3: ", res);
}, err => {
console.log("err3: ", err);
})
}, 1000)

setTimeout 会在主线程的任务执行完,并且微任务队列也执行完后才执行

捋一捋执行顺序:

  1. 首先主线程 resolve("111")执行,先改变状态为fulfilled,保存value,然后在微任务队列中加入一个任务

    image.png
  2. 然后继续往下,setTimeout 被加入到宏任务队列,并未执行then

  3. 主线程任务执行完,执行微任务队列中的任务,遍历数组中的函数并执行,但我们可以看到,前面then没有执行,数组中并没有对应回调,所以没有执行

  4. 接着执行宏任务队列中setTimeout,也就是then方法被执行,

    image.png

    这个时候才把回调加入数组。。。任务结束

因此可以想到实现思路:在then方法中加入判断,如果当前状态是确定的,(fulfilled或rejected),那么直接执行这个回调,不用添加到数组

then(onFulfilled, onRejected) {
if (this.status === STATUE_FULFILLED && onFulfilled) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED && onRejected) {
onRejected(this.reason)
}

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
}

链式调用then

then需要返回一个promise,后面才能继续调用then

并且下一次的then是在上次回调执行完之后才执行的,而且需要拿到上次onFulfilled/onRejected 执行完之后的结果,再去执行resolve,reject

then(onFulfilled, onRejected) {
if (this.status === STATUE_FULFILLED && onFulfilled) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED && onRejected) {
onRejected(this.reason)
}

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}

return new MyPromise((resolve, reject) => {
resolve() // 这里需要拿到上次回调的结果并且传进去
reject()
})
}

现在有个问题,就是怎么拿到上次执行的结果值?

image.png

思路:把上面那一坨代码放到new里面执行,这样就让里边具备了拿到返回结果的能力

then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === STATUE_FULFILLED && onFulfilled) {
// 比如这里就可以拿到结果
const value = onFulfilled(this.value)
// 然后再执行(这里已经是下一个promise的resolve了)
resolve(value)
}
if (this.status === STATUS_REJECTED && onRejected) {
const reason = onRejected(this.reason)
// 注意这里调用的应该是resolve,除非上一次的错误回调函数抛出了错误
resolve(reason)
}

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
}

因为即使我们不链式调用,里面的代码还是会执行的,我们只是想拿到想要的结果,并且在某个时机调用 resolve, reject

两种情况都调用resolve?那什么时候调用reject呢?

我们知道,只有在上次的then中抛出异常了,下次才会执行reject

所以我们再改进一下:使用try…catch

then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === STATUE_FULFILLED && onFulfilled) {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch (err) {
reject(err)
}
}
if (this.status === STATUS_REJECTED && onRejected) {
try {
const reason = onRejected(this.reason)
resolve(reason)
} catch (err) {
reject(err)
}
}

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
}

上面都是在状态已经确定之后的情况,我们知道在状态为pending的时候,我们是把回调保存到数组,然后在上面的resolve、reject中遍历数组执行的

this.onFulfilledCallbacks.forEach(fn => {  
let value = fn(this.value) // 在这里执行的
})

那这个执行结果怎么拿到呢然后传给下面的resolve、reject呢?

解决方案:改进我们添加到数组的方式

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(() => {
onFulfilled()
})
this.onRejectedCallbacks.push(() => {
onRejected()
})
}

我们不直接把回调添加到数组,而是使用一个函数包裹起来,那么到时上面遍历执行的就是这个包裹的函数,然后我们在这里内部执行onFulfilled,就可以在这里拿到onFulfilled的执行结果

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(() => {
const value = onFulfilled(this.value)
resolve(value)
})
this.onRejectedCallbacks.push(() => {
const reason = onRejected(this.reason)
resolve(reason)
})
}

同理,try…catch改进一下

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch (err) {
reject(err)
}
})
this.onRejectedCallbacks.push(() => {
try {
const reason = onRejected(this.reason)
resolve(reason)
} catch (err) {
reject(err)
}
})
}

测试的时候发现,有一种情况我们没有处理

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
// resolve("111")
// reject("222")
throw new Error("error message")
})

改进,在 executor 那里加try…catch,如果抛出了异常直接执行reject

try {
executor(resolve, reject)
} catch (err) {
reject(err)
}

测试:

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
throw new Error("error message")
})

promise.then(res => {
console.log("res: ", res);
return "aaa"
}, err => {
console.log("err: ", err); // 执行这里
return "bbb"
}).then(res => {
console.log("res2: ", res); // 然后来到这里
}, err => {
console.log("err2: ", err);
})
image.png

测试没问题!!

工具函数

到这里then方法基本就实现完了,但是我们发现很多 try…catch的重复代码

我们可以做一个抽取,定义一个工具函数

function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
const result = exeFn(value)
resolve(result)
} catch (err) {
reject(err)
}
}

然后优化then方法:

then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === STATUE_FULFILLED && onFulfilled) {
// try {
// const value = onFulfilled(this.value)
// resolve(value)
// } catch (err) {
// reject(err)
// }
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === STATUS_REJECTED && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}

if (this.status === STATUE_PENDING) {
this.onFulfilledCallbacks.push(() => {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
this.onRejectedCallbacks.push(() => {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}

catch 方法的实现

catch接收一个参数,也就是失败的回调,因为catch实际上就相当于语法糖,而且也是返回一个新的promise,所以我们可以直接调用then

catch(onRejected) {
return this.then(undefined, onRejected)
}

测试:

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
// resolve(111)
reject(222)
})

promise.then(res => {
console.log("res: ", res);
return "aaa"
}).catch(err => {
console.log("catch", err);
})

额,报错了

image.png

别急,分析原因。

我们可以观察一下,本来我们的失败回调应该是在第一个then中传入的,是作为第一个promise的失败的回调的

promise.then(res => {
console.log("res: ", res);
return "aaa"
}, err => {
console.log("catch", err);
}).catch()

但是现在我们写到catch里面,意味着这个catch传入的回调是被加入到一个新的promise中,作为新的promise的失败的回调了,所以执行不到这个回调了

promise1 -> undefined

promise2 -> err ={}

那么我们怎么样让promise1执行到err ={} 呢??

实现思路:假如说promise1的失败回调没有值,我们直接抛出去,这样就会来到promise2中失败的回调了

promise1 -> err => {throw err} // 抛出去

promise2 -> err ={} // 就会执行到这里

还有一个地方是,既然onFulfilled或onRejected可能会传进来undefined,那么我们最好加上一个判断

then(onFulfilled, onRejected) {
const defaultOnRejected = err => { throw err } // 添加部分
onRejected = onRejected || defaultOnRejected

return new MyPromise((resolve, reject) => {
if (this.status === STATUE_FULFILLED && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === STATUS_REJECTED && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}

if (this.status === STATUE_PENDING) {
if (onFulfilled) this.onFulfilledCallbacks.push(() => {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
if (onRejected) this.onRejectedCallbacks.push(() => {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}

finally 方法的实现

接收一个参数,无论成功或失败,最后都会执行

也就是说,成功与失败的回调都是你传入的那个函数

// finally
finally(onFinally) {
return this.then(() => {
onFinally()
}, () => {
onFinally()
})
}

但是还会有一个问题

const promise = new MyPromise((resolve, reject) => {
console.log("pending~");
resolve(111) // 调用resolve有问题
// reject(222) 调用reject没问题
})

promise.then(res => {
console.log("res: ", res); // 圈1
}).catch(err => {
console.log("catch", err);// 圈2
}).finally(() => {
console.log('finally'); // 圈3
})

分析:捋一捋

如果上面是resolve的话,那么就会执行圈1,然后来到promise2的成功回调

promise1:成功回调是 圈1,失败回调是我们默认的reject

promise2:没有成功回调,失败回调是圈2,

​ 没有成功回调,这时候就会出错了,后面也不会继续执行了,因此要给默认的成功回调 value => { return value }

然后才会执行finally的第一个回调

then(onFulfilled, onRejected) {
const defaultOnRejected = err => { throw err }
onRejected = onRejected || defaultOnRejected

// 添加部分
const defaultOnFulfilled = value => { return value }
onFulfilled = onFulfilled || defaultOnFulfilled

// 来到这里就可以保证 onRejected,onFulfilled 是有值的了,所以下面的判断也可以不要了
return new MyPromise((resolve, reject) => {
if (this.status === STATUE_FULFILLED) { // 改了这里
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === STATUS_REJECTED) { // 改了这里
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}

if (this.status === STATUE_PENDING) {
if (onFulfilled) this.onFulfilledCallbacks.push(() => {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
if (onRejected) this.onRejectedCallbacks.push(() => {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}

resolve reject 类方法的实现

resolve 类方法本质就是new了一个 promise,然后直接调用resolve

reject同理

static resolve(value) {
return new MyPromise((resolve) => resolve(value))
}

static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}

测试:

MyPromise.resolve(123).then(res => {
console.log(res); // 123
})

all 方法的实现

all方法接收一个promise数组,同样返回一个promise

关键思路:什么时候要执行resolve, 什么时候要执行reject

static all(promises) {
return new MyPromise((resolve, reject) => {
const values = [] // 定义结果数组
promises.forEach(promise => {
promise.then(res => {
values.push(res)
// 当结果的个数等于传入promise个数的时候,表示已经执行完了,然后再回调resolve
if (values.length === promises.length) {
resolve(values)
}
}, err => {
reject(err) // 失败直接执行回调reject
})
})
})
}

测试:

const p1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve("111")
}, 1000);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("222")
// reject("222")
}, 2000);
})

const p3 = new MyPromise((resolve) => {
setTimeout(() => {
resolve("333")
}, 3000);
})

MyPromise.all([p1, p2, p3]).then(res => {
console.log(res); // ['111', '222', '333']
}, err => {
console.log(err); // 如果p2调用reject,就会执行这里,输出 222
})

allSettled 方法的实现

与all类型,区别就是无论成功与否都会把结果保存到数组

测试:

const p1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve("111")
}, 1000);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("222")
// reject("222")
}, 2000);
})

const p3 = new MyPromise((resolve) => {
setTimeout(() => {
resolve("333")
}, 3000);
})

MyPromise.allSettled([p1, p2, p3]).then(res => {
console.log(res);
})
image.png

race 方法的实现

只要状态改变就执行回调

static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
// promise.then(res => {
// resolve(res)
// }, err => {
// reject(err)
// })
// 也可以这么写
promise.then(resolve, reject)
})
})
}

any 方法的实现

等到第一个fulfilled状态,才执行resolve回调

如果全都是rejected状态,那么抛出异常

static any(promises) {
const reasons = []
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
promise.then(res => {
resolve(res)
}, err => {
reasons.push(err)
// 当全部promise执行完之后
if (reasons.length === promises.length) {
reject(new AggregateError(reasons))
}
})
})
})
}

测试全都是reject的情况

const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("111")
}, 1000);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("222")
}, 2000);
})

const p3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("333")
}, 3000);
})

MyPromise.any([p1, p2, p3]).then(res => {
console.log(res);
}, err => {
console.log(err, err.errors);
})
image.png

到此,手写promise就差不多大功告成啦!