✔️自己动手实现一个Promise类!

305 阅读7分钟

Promise可以说是JavaScript中异步宏任务的代表,要想真正理解的Promise就得从其源码角度,所以这里我们尝试用JavaScript来实现一个Promise类。

要求

根据Promise/A+原理,有以下几个点需要遵守:

  1. Promise有一个Promise States,其有三种状态:

    1. pending:初始状态,可以转化为fulfilledrejected
    2. fulfilled
      1. 需要一个value作为结果。
      2. 不能转化为其他状态。
    3. rejected
      1. 需要一个reason作为一个原因。
      2. 不能转化为其他状态。
  2. 必须要一个then方法,接受两个参数

    1. onFulfilled :当操作成功时发生的回调。
    2. onRejected :当操作失败时发生的回调。

    一旦promise完成,每一次调用then方法,得到的结果必须是相同的。

  3. Promise返回的必须也是一个promise

原API兼容性

Promise image.png

开发

实现1,2功能

首先我们实现第一个和第二个特点:

基本思路如下:

  • 一个Promise首先要有一个status,表示当前的状态,其有三种状态:

    1. PENDING:表示当前Promise未完成,此时的回调函数会加入相应的队列
    2. RESOLVED:表示当前Promise已经完成,且时resolve状态,直接调用成功的函数。
    3. REJECTED:表示当前Promise已经完成,且时rejected状态,直接调用失败的函数。
  • 然后我们要定义两个值:

    1. value:用于resolve时的返回值。
    2. reason:用于rejected时的返回值。
  • 上面说到当Promise状态为PENDING时,我们会把回调函数放到相应的队列,所以我们还会定义两个队列

    1. resolvedCallQueue:存放成功的回调函数
    2. rejectedCallQueue:存放失败的回调函数
  • 然后我们定义Promiseresolverejected函数,在这两个函数中,我们会改变Promise的状态并且给value或者reason赋值,最后执行相应的函数队列。

  • 最后我们会定义Promisethen方法。如同上面对于状态的定义,我们会根据当前Promise的状态来处理then方法传来的回调函数。

下面用ES6class来实现:

class Promise {
  constructor(executer) {
    //定义Promise状态枚举数据
    this.status_enum = {
      PENDING: 'PENDING',
      RESOLVED: 'RESOLVED',
      REJECTED: 'REJECTED',
    };
    //成功的返回值
    this.value = undefined;
    //失败的返回值
    this.reason = undefined;
    //成功的回调函数队列
    this.resolvedCallQueue = [];
    //失败的回调函数队列
    this.rejectedCallQueue = [];
    //当前Promise状态
    this.status = this.status_enum.PENDING;
    //Promise完成
    let resolve = (value) => {
      if (this.status === this.status_enum.PENDING) {
        this.status = this.status_enum.RESOLVED;
        this.value = value;
        for (let i = 0; i < this.resolvedCallQueue.length; i++) {
          this.resolvedCallQueue[i](this.value);
        }
      }
    };
    //Promise失败
    let reject = (reason) => {
      if (this.status === this.status_enum.PENDING) {
        this.status = this.status_enum.REJECTED;
        this.reason = reason;
        for (let i = 0; i < rejectedCallQueue.length; i++) {
          this.rejectedCallQueue[i](this.reason);
        }
      }
    };
    //执行Promise函数
    try {
      executer(resolve, reject);
    } catch (e) {
      console.log('错误', e);
      reject(e);
    }
  }
  //定义then函数
  then(onfulfilled, onrejected) {
    //检测参数必须为函数
    if(!onfulfilled instanceof Function || !onrejected instanceof Function){
      throw new Error('Uncaught TypeError: Promise resolver is not a function')
    }
    //当Promise状态为RESOLVED时,进行成功回调函数
    if (this.status === this.status_enum.RESOLVED) {
      onfulfilled(this.value);
    }
    //当Promise状态为REJECTED时,进行失败回调函数
    if (this.status === this.status_enum.REJECTED) {
      onrejected(this.reason);
    }
    //当Promise状态为PENDING时,将其回调函数加入相应队列,在完成时会执行相应的函数队列
    if (this.status === this.status_enum.PENDING) {
      this.resolvedCallQueue.push(onfulfilled);
      this.rejectedCallQueue.push(onrejected);
    }
  }
}

下面用原型实现(基本原理一样,因为class本质也是原型链的语法糖):

function Promise(executer) {
  this.status_enum = {
    PENDING: 'PENDING',
    RESOLVED: 'RESOLVED',
    REJECTED: 'REJECTED',
  };
  this.value = undefined;
  this.reason = undefined;
  this.resolvedCallQueue = [];
  this.rejectedCallQueue = [];
  this.status = this.status_enum.PENDING;
  let resolve = (value) => {
    if (this.status === this.status_enum.PENDING) {
      this.status = this.status_enum.RESOLVED;
      this.value = value;
      for (let i = 0; i < this.resolvedCallQueue.length; i++) {
        this.resolvedCallQueue[i](this.value);
      }
    }
  };
  let reject = (reason) => {
    if (this.status === this.status_enum.PENDING) {
      this.status = this.status_enum.REJECTED;
      this.reason = reason;
      for (let i = 0; i < rejectedCallQueue.length; i++) {
        this.rejectedCallQueue[i](this.reason);
      }
    }
  };
  try {
    executer(resolve, reject);
  } catch (e) {
    console.log('错误', e);
    reject(e);
  }
}
Promise2.prototype.then = function then(onfulfilled, onrejected) {
  if (this.status === this.status_enum.RESOLVED) {
    onfulfilled(this.value);
  }
  if (this.status === this.status_enum.REJECTED) {
    onrejected(this.reason);
  }
  if (this.status === this.status_enum.PENDING) {
    this.resolvedCallQueue.push(onfulfilled);
    this.rejectedCallQueue.push(onrejected);
  }
};

这样,Promise的基本功能就已经实现,简单测试如下:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('2秒!!!');
        resolve(111);
    }, 2000);
});
p.then((res) => {
    console.log('p1');
    console.log(res);
});
p.then((res) => {
    console.log('p2');
    console.log(res);
});

结果:

2秒!!!

p1

111

p2

111

实现1,2,3功能

前面我们基本实现了Promise的基本功能,但是还是一个问题是:Promise必须返回一个Promise,上面的代码并不满足这一点。

所谓我们为了实现返回的都是Promise,我们需要重写then方法,使之返回的是一个Promise,这时,我们还是要根据当前Promise的状态来分开来处理。

但是其中心点是,必须持续执行Promise,直到其返回值不是一个thenable对象或方法,所以这是一个递归的过程。所以我们定义了一个函数cycleResolve,其接受四个参数:

  1. newPromise:新建的被用于返回的Promise
  2. target:回调函数执行的获得结果(可能仍然是一个Promise
  3. resolve:新Promiseresolve函数
  4. reject:新Promisereject函数

该函数的功能是:判定返回值target是不是一个thenable对象(包括Promise),如果是,继续执行其then方法。经过这个过程,知道返回值不是thenable对象,然后将其值resolve出去。

注意:

  1. 只有target是一个thenable对象并且其then属性是一个函数时,才会调用其then方法,否则会直接将targetresolve出去。
  2. 一旦遇到错误,都会直接reject(e)
  3. then方法中必须使用setTimeout来使内部的操作成为宏任务,在下一个tick执行。这样才能拿到newPromise,否则会出现未初始化错误。
class Promise {
    constructor(executer) {
        this.status_enum = {
            PENDING: 'PENDING',
            RESOLVED: 'RESOLVED',
            REJECTED: 'REJECTED',
        };
        this.value = undefined;
        this.reason = undefined;
        this.resolvedCallQueue = [];
        this.rejectedCallQueue = [];
        this.status = this.status_enum.PENDING;
        //定义resolve方法,任务完成时调度
        this.resolve = (value) => {
            if (this.status === this.status_enum.PENDING) {
                this.status = this.status_enum.RESOLVED;
                this.value = value;
                for (let i = 0; i < this.resolvedCallQueue.length; i++) {
                    this.resolvedCallQueue[i](this.value);
                }
            }
        };
        //定义reject方法,任务失败时调度
        this.reject = (reason) => {
            if (this.status === this.status_enum.PENDING) {
                this.status = this.status_enum.REJECTED;
                this.reason = reason;
                for (let i = 0; i < this.rejectedCallQueue.length; i++) {
                    this.rejectedCallQueue[i](this.reason);
                }
            }
        };
        //执行定义Promise时传入的任务
        try {
            executer(this.resolve, this.reject);
        } catch (e) {
            console.log('错误', e);
            this.reject(e);
        }
    }
    //定义then方法
    then(onfulfilled, onrejected) {
        //新建一个Promise用于返回
        let newPromise = new Promise((resolve, reject) => {
            if (this.status === this.status_enum.RESOLVED) {
                setTimeout(() => {
                    try {
                        let target = onfulfilled(this.value);
                        cycleResolve(newPromise, target, resolve, reject);
                    } catch (e) {
                        console.log(e);
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === this.status_enum.REJECTED) {
                //建立宏任务,方便拿到newPromise,否则会出现未初始化错误
                setTimeout(() => {
                    try {
                        let target = onrejected(this.reason);
                        cycleResolve(newPromise, target, resolve, reject);
                    } catch (e) {
                        console.log(e);
                        reject(e);
                    }
                });
            }
            if (this.status === this.status_enum.PENDING) {
                this.resolvedCallQueue.push(() => {
                    setTimeout(() => {
                        try {
                            let target = onfulfilled(this.value); //将resolve函数保留的成功值传递作为参数
                            cycleResolve(newPromise, target, resolve, reject);
                        } catch (e) {
                            console.log(e);
                            reject(e);
                        }
                    }, 0);
                });
                this.rejectedCallQueue.push(() => {
                    setTimeout(() => {
                        try {
                            let target = onrejected(this.reason); //将resolve函数保留的成功值传递作为参数
                            cycleResolve(newPromise, target, resolve, reject);
                        } catch (e) {
                            console.log(e);
                            reject(e);
                        }
                    }, 0);
                });
            }
        });
        return newPromise;
    }

    function cycleResolve(newPromise, target, resolve, reject) {
        //禁止循环调用
        if (newPromise === target) {
            return reject(new TypeError('循环调用'));
        }
        if (
            target !== null &&
            (typeof target === 'object' ||
             typeof target === 'function') /*确定其是一个对象*/
        ) {
            try {
                let then = target.then; /*确定是否是一个thenable对象*/
                if (typeof then === 'function') {
                    then.call(
                        target,
                        (newTarget) => {
                            resolvePromise(newPromise, newTarget, resolve, reject);
                        },
                        (e) => {
                            reject(e);
                        }
                    );
                } else {
                    resolve(target);
                }
            } catch (e) {
                reject(e);
            }
        } else {
            resolve(target);
        }
    }

至此,Promise的基本功能都已经完成,接下来我们完成剩下的一些细枝末节的东西,包括:

  1. 实现resolve方法
  2. 实现reject方法
  3. 实现catch方法。
  4. 实现finally方法。
  5. 实现Promise的其他方法:
    1. Promise.all([p1, p2, p3])
    2. Promise.race([p1, p2, p3])
    3. Promise.allSettled(p1, p2, p3)
    4. Promise.any(p1, p2, p3)

注意:

  1. 各个函数的功能不再赘述,具体查看Promise理解
  2. 下面的代码就只写对应部分,多余部分不再进行赘述。

实现catch方法

catch(onrejected) { /*在class中定义catch方法*/
    const newPromise = new Promise3((undefined, reject) => {
        if (this.status === this.status_enum.REJECTED) {
            setTimeout(() => {
                try {
                    let target = onrejected(this.reason);
                    cycleReject(newPromise, target, reject);
                } catch (e) {
                    console.log(e)
                    reject(e);
                }
            }, 0);
        }
        if (this.status === this.status_enum.PENDING) {
            this.rejectedCallQueue.push(() => {
                setTimeout(() => {
                    try {
                        let target = onrejected(this.reason);
                        cycleReject(newPromise, target, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            });
        }
    });
}
}

//循环reject
function cycleReject(newPromise, target, reject) {
    //禁止循环调用
    if (newPromise === target) {
        return reject(new TypeError('循环调用'));
    }
    if (
        target !== null &&
        (typeof target === 'object' ||
         typeof target === 'function') /*确定其是一个对象*/
    ) {
        try {
            let then = target.then; /*确定是否是一个thenable对象*/
            if (typeof then === 'function') {
                then.call(
                    target,
                    (newTarget) => {
                        resolvePromise(newPromise, newTarget, reject);
                    },
                    (e) => {
                        reject(e);
                    }
                );
            }
        } catch (e) {
            reject(e);
        }
    }else{
        reject(target)
    }
}

其实这里的操作与then方法逻辑基本一致,唯一不同的是,我们只需要捕捉reject,不捕捉resolve

原API兼容性

image.png

实现finally

Promisefinally的方法无论promise的结果是成功还是失败,都会执行,并且返回该promise。所以实现很简单。执行其promisethen方法来获取该promise的结果。

finally(callback) {
    callback();
    return this.then(() => {}, () => {});
}

原API兼容性

image.png

实现Promise.resolve方法(下面的方法是直接作为class的静态函数成员的)

static resolve(val) {
    //如果是一个Promise
    if(val instanceof Promise3){
        return val
    //如果没有参数,或者为null,undefined
    }else if(!val){
        return new Promise3((resolve, reject) => {resolve()})
    //参数存在但不存在then方法
    }else if(val && !val.then instanceof Function){
        return new Promise3((resolve) => {
            resolve(val);
        });
    //参数存在且存在then方法
    }else if(val && val.then instanceof Function){
        return new Promise3(val.then)
    }
}

原API兼容性

image.png

实现Promise.reject方法

static reject(val){
    //直接将val作为理由返回
    return new Promise((resolve,reject)=>{
        reject(val);
    })
}

原API兼容性

image.png

实现Promise.all([p1, p2, p3])

static all(arr) {
    let res = []
    return new Promise((resolve, reject) => {
        for(let i = 0, len = arr.length; i < len; i++){
            arr[i].then((v) => {
                res.push(v)
                //是否所有的promise都是resolve
                if(res.length === arr.length){
                    return resolve(res)
                }
            }, (e) => {
                //只要一个reject,直接reject
                return reject(e)
            })
        }
    })
}

原API兼容性

image.png

实现Promise.race([p1, p2, p3])

static race(arr) {
    return new Promise((resolve, reject) => {
        for (let i = 0, len = arr.length; i < len; i++) {
            arr[i].then(
                (v) => {
                    return resolve(v)
                },
                (e) => {
                    return reject(e)
                }
            );
        }
    });
}

原API兼容性

image.png

实现Promise.allSettled([p1, p2, p3])

static allSettled(arr) {
    let res = [];
    let count = 0;
    return new Promise((resolve, reject) => {
        for (let i = 0, len = arr.length; i < len; i++) {
            arr[i].then(
                (v) => {
                    res.push({
                        status: 'fulfilled',
                        value: v,
                    });
                },
                (e) => {
                    res.push({
                        status: 'rejected',
                        reason: e,
                    });
                }
            ).finally(() => {
                //计数器必须在这里统计,因为异步操作,若放在then中,在finally中无法读取预期的值
                ++count === arr.length && resolve(res)
            });
        }
    });
}

原API兼容性

image.png

实现Promise.any(p1, p2, p3)

static any(arr) {
    let res = [];
    return new Promise((resolve, reject) => {
        for (let i = 0, len = arr.length; i < len; i++) {
            arr[i].then(
                (v) => {
                    //只要有一个resolve,则直接resolve
                    return resolve(v);
                },
                (e) => {
                    res.push(e);
                    res.length === arr.length && reject(res);
                }
            );
        }
    });
}
}

原API兼容性

image.png

最后

本人能力有限,难免出现纰漏失误,请大家指出纠正。

看看这

更多有趣文章都在公众号-全站学习小师兄。