前言
Promise作为成熟的异步解决方案,相信大家对其用法已经滚瓜烂熟了。 那有这时间为什么不去学点别的东西,费劲吧啦把这个实现一遍?因为只停留在会使用的程度,很难说真正掌握了Promise. 有些已有认知可能是错的,在手打一遍的过程中会有更深一层的理解。2021年已近尾声,不管你是几年经验的前端,如果还没手打过一遍Promise, 那就用这篇文章把这个todo勾上吧。
注:此文假定你已经明白Promise的概念及用法。
先把架子搭起来
我们这次用es6的class来实现,先来看看Promise的常规用法。new一个实例,传入一个函数,这个函数接收两个参数。调用resolve能把当前promise的状态变更为已完成,调用reject则可以把当前promise的状态变更为已拒绝。then里面执行一些后续操作,catch里面处理异常情况。
new Promise((resolve, reject)=>{
setTimeout(() => {
resolve(123)
},1000) // 异步操作
}).then((value) => {
// doSomeThing
}).catch((error)=>{
// oh no
})
我们把山寨Promise叫MyPromise, 声明三个状态常量,定义三个实例属性。并把传入的fn立即执行掉。resolve和reject这两个类方法用来变更myPromise的状态,为何要绑一下this,因为要防止有人吃饱了撑的把这两个函数的this指向改掉。另外还要try catch包一下,函数调用出错则立即拒绝掉myPromise.
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise{
_status = PENDING //初始状态
constructor(fn){
this.value = null // 决议值
this.reason = null // 拒绝原因
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (e){
this.reject(e)
}
}
}
我们把resovle和reject也写出来,细心的你应该看出来了,这个status没有下横岗。并不是作者垃圾,而是status要用getter setter来实现。用来监听状态变化,做一些事情。
{
resolve(value){
if(this.status === PENDING){
this.value = value
this.status = FULFILLED
}
}
reject(reason){
if(this.status === PENDING){
this.reason = reason
this.status = REJECTED
}
}
}
status我们也写一下,聪明的你应该看出来_status是一个中间变量,供getter setter函数使用。
{
get status(){
return this._status
}
set status(newStatus){
this._status = newStatus
switch(newStatus){
case FULFILLED:
this.FULFILLED_CALLBACK_LIST.forEach(callback => callback(this.value))
break
case REJECTED:
this.REJECTED_CALLBACK_LIST.forEach(callback => callback(this.reason))
break
}
}
}
FULFILLED_CALLBACK_LIST和REJECTED_CALLBACK_LIST又是啥?看调用了forEach应该是个数组,那这是存啥的?FULFILLED_CALLBACK_LIST里存的是then的第一个函数参数,REJECTED_CALLBACK_LIST存的是then的第二个函数参数。
...
class MyPromise{
FULFILLED_CALLBACK_LIST = []
REJECTED_CALLBACK_LIST = []
_status = PENDING //初始状态
constructor(fn){
...
那为啥放到数组里存,难道还能有多个?还真可以有多个,按照如下的写法,当myPromise的状态变更,就遍历对应数组逐个调用。我在业务中还没有遇到过这种场景。
const p = new MyPromise((resolve) => resolve(1))
p.then(value => console.log(value))
p.then(value => console.log(value))
p.then(value => console.log(value))
p.then(value => console.log(value))
p.then(value => console.log(value))
p.then(value => console.log(value))
then
提到then, 有如下3个特性
- 接收两个函数参数,第一个函数接收
决议值,第二个函数接收拒绝理由 - 传入
then的函数,要塞进微任务队列 then调用后返回一个promise我们按照这三个特性,把轮廓画一下。
{
...
then(onFulfilled, onRejected){
onFulfill = this.isFn(onFulfilled) ? onFulfilled : value => value
onReject = this.isFn(onRejected) ? onRejected : reason => { throw reason }
const myPromiseCreatedByThen = new MyPromise((resolve, reject)=>{
...
})
return myPromiseCreatedByThen
}
...
}
isFn是一个工具函数内部为 typeof === 'function', 就一笔带过了。这里要判断两个形参是否为函数,如果不是函数,就赋值默认函数。为什么这么做呢,因为人家PromiseA+规范就是这么制定滴。
onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略
onRejected 必须是函数类型, 如果不是函数, 应该被忽略 画完了轮廓,该填充内容了。我们把返回的myPromise内部也写一下。
{
...
then(onFulfilled, onRejected){
...
const myPromiseCreatedByThen = new MyPromise((resolve, reject)=>{
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value)
this.resovlePromise(myPromiseCreatedByThen, res, resolve, reject)
} catch (e){
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const res = onRejected(this.reason)
this.resovlePromise(myPromiseCreatedByThen, res, resolve, reject)
} catch (e){
reject(e)
}
})
}
})
return myPromiseCreatedByThen
}
...
}
先创建两个函数fulfilledMicrotask和rejectedMicrotask. 这两个是状态变化时要调用的函数。这里要注意的是queueMicrotask函数,此函数并非我们定义的,而是浏览器提供的。作用就是把参数塞进微任务队列。而resolvePromise里做的,就是用onFulfilled或onRejected调用之后的返回值,改变then返回的promise的状态。 下节会详细讲到。
咱们继续,接下来要分两种情形,第一种是状态被立刻改变。比如这种
new MyPromise((resolve, reject) => {
resolve(1)
// reject('oh no') 或者拒绝
}).then(value => console.log(value), reason => console.log(reason))
这时我们要马上塞入微任务队列。
{
...
then(onFulfilled, onRejected){
...
const myPromiseCreatedByThen = new MyPromise((resolve, reject)=>{
...
switch(this.status){
case FULFILLED:
fulfilledMicrotask()
break
case REJECTED:
rejectedMicrotask()
break
...
}
})
return myPromiseCreatedByThen
}
...
}
第二种是状态被异步改变,比如这种
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve(1)
// reject('oh no') 或者拒绝
}, 1000)
}).then(value => console.log(value), reason => console.log(reason))
这时我们要把传进then里的函数先放到临时数组里,待监听到status变化时再调用。
{
...
then(onFulfilled, onRejected){
...
const myPromiseCreatedByThen = new MyPromise((resolve, reject)=>{
...
switch(this.status){
...
case PENDING:
FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
break
}
})
return myPromiseCreatedByThen
}
...
}
resolvePromise
在上一节我们在fulfilledMicrotask和rejectedMicrotask里调用了resovlePromise. 那这玩意儿是干啥的?
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value)
this.resovlePromise(myPromiseCreatedByThen, res, resolve, reject)
} catch (e){
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const res = onRejected(this.reason)
this.resovlePromise(myPromiseCreatedByThen, res, resolve, reject)
} catch (e){
reject(e)
}
})
}
有时我们在传入then的函数里也会返回一个promise, 而这个promise的状态会决定then返回的promise的状态。
new Promise((resolve, reject) => {
resolve(1)
// reject('oh no') 或者拒绝
}).then(value => {
return new Promise((resolve, reject) => {
resolve(1)
})
}, reason => {
// 拒绝走这里
return new Promise((resolve, reject) => {
resolve(1)
})
})
我们再看传入resovlePromise的参数, myPromiseCreatedByThen是then调用后要return的myPromise, res是上面代码块传入then的函数的返回值,也就是一个promise. 当然它也可能是非promise的任何值。 resolve和reject是用来变更myPromiseCreatedByThen状态的两个函数。
{
...
then(onFulfilled, onRejected){
...
const myPromiseCreatedByThen = new MyPromise((resolve, reject)=>{
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value)
this.resovlePromise(myPromiseCreatedByThen, res, resolve, reject)
} catch (e){
reject(e)
}
})
}
...
})
return myPromiseCreatedByThen
}
...
}
好,我们进入resovlePromise内部一探究竟。
{
resovlePromise(myPromiseCreatedByThen, res, resolve, reject){
if(myPromiseCreatedByThen === res){
reject(new TypeError('The promise and the return value are the same'))
}
...
}
}
首先会比较myPromiseCreatedByThen和传入then的函数调用后返回的值是否严格相等。如果严格相等,就用一个错误对象拒绝掉myPromiseCreatedByThen.这是PromiseA+规范里规定的一个异常处理情况。为了防止如下的骚操作。
const myPromise1 = new MyPromise(resolve => {
resolve(123)
}).then(value => {
return myPromise1
})
// 此时的myPromise1为then返回的myPromise,如果没有上面的严格相等判断会陷入死循环
接着会判断res是否为一个myPromise, 如果是,则会在res.then的第一个函数参数里递归调用resolvePromise. 注意这次调用resolvePromise时的四个参数中,第二个参数变为了res的决议值, 其他没有变化。这样就做到了用res的状态决定myPromiseCreatedByThen的状态。这里涉及到递归,需要从使用者和设计者之间来回的视角切换。可能需要多想一会儿🤔,这部分可以说是Promise的精髓了。
{
resovlePromise(myPromiseCreatedByThen, res, resolve, reject){
...
if(res instanceof MyPromise){
queueMicrotask(()=>{
res.then(value => {
this.resolvePromise(myPromiseCreatedByThen, value, resolve, reject)
}, reject)
})
}else if ...
}
}
这里先引入一个叫thenable的概念。
thenable 是一个有then方法的对象或者是函数 接下来的分支就是处理
thenable的。当res为对象或函数,取出then方法。如果then为函数,就可以视作thenable处理了。如果then不为函数,就把res作为决议值完成掉myPromiseCreatedByThen.
{
resovlePromise(myPromiseCreatedByThen, res, resolve, reject){
...
if(res instanceof MyPromise){
...
} else if(typeof res === 'object' || this.isFn(res)){
if(res === null){
resolve(res)
}
let then
try {
then = res.then
} catch (e){
reject(e)
}
if(this.isFn(then)){
...
}else{
resolve(res)
}
} else ...
}
}
下面处理res为thenable时的情况。以res为上下文调用then, 两个函数参数与Promise的then相同,大体上和上面res为MyPromise的分支差不多。要注意的是需要定义一个标记变量,防止重复调用。因为我们不知道thenable是怎么实现的,万一是个垃圾呢,所以要防止它重复更改myPromiseCreatedByThen的状态。
if(this.isFn(then)){
let isCalled = false
try {
then.call(res,
(valueOfThenable) => {
if(isCalled){
return
}
isCalled = true
this.resolvePromise(myPromiseCreatedByThen, valueOfThenable, resolve, reject)
},
(reasonOfThenable) => {
if(isCalled){
return
}
isCalled = true
reject(reasonOfThenable)
}
)
} catch (e){
if(isCalled){
return
}
reject(e)
}
}else{
resolve(res)
}
最后的else, 就是基本类型的情况了,直接resolve即可。
{
resovlePromise(myPromiseCreatedByThen, res, resolve, reject){
...
if(res instanceof MyPromise){
...
} else if(typeof res === 'object' || this.isFn(res)){
...
} else {
resolve(res)
}
}
}
catch
不知道大家是习惯在then里传两个函数,还是在then后面再接一个catch. 感觉接catch的形式可读性要好一些,所以我一直用这种方式。觉得catch也要写一大坨?可能比预想的要简单,但是需要多琢磨一下。
{
catch(onRejected){
return this.then(null, onRejected)
}
}
一些常用的静态方法
这四个方法实现起来比较简单,就不多说了。
resolve
{
static resolve(value){
if(value instanceof MyPromise){
return value
}
return new MyPromise((resolve) => {
resolve(value)
})
}
}
reject
{
static reject(reason){
return new MyPromise((null, reject) => {
reject(reason)
})
}
}
race
{
race(promiseArr){
return new MyPromise((resolve, reject) => {
const length = promiseArr.length
if(length === 0){
resolve()
}
for(let i = 0; i < length; i++){
MyPromise.resolve(PromiseArr[i]).then(value => {
return resolve(value)
}, (reason) => {
return reject(reason)
})
}
})
}
}
all
{
all(promiseArr){
return new MyPromise((resolve, reject)=>{
const fulfilledValueArr = []
const length = promiseArr.length
let fulfilledCount = 0
for(let i = 0; i < length; i++){
MyPromise.resolve(promiseArr[i]).then((value) => {
fulfilledValueArr[i] = value
fulfilledCount++
if(fulfilledCount === length){
resolve(fulfilledValueArr)
}
}, (reason) => {
reject(reason)
})
}
})
}
}
结语
希望在读过本文之后,能让你加深对Promise的理解。行文仓促,难免有疏漏或不足,还望不吝指出。