2023-05-30 重温ES6之Promise并手写终极源码

554 阅读9分钟

看了很多版本的Promise手写源码,感觉都差那么点儿意思,直到看到抖音某一位大佬对Promise的阐述,发现理解的真深刻,于是自己动手捋清楚这里面的逻辑,整理成笔记如下

Promise最核心的其实就是两个地方:一个是其构造函数constructor;一个是其里面的then方法,搞定这两个核心问题,其他的都是细枝末节。解决这两个问题,其他的都呼之欲出了。

1、MyPromise的构造函数

Promise的用法

const p = new Promise((resolve, reject) => {
    resolve(1);
})

其作为构造函数通过new调用,传入的参数其实是一个函数,姑且称之为执行器-executor,而且这个执行器也接受两个实参resolve和reject,这两个参数也是一个函数,调用这两个函数其中一个就可以改变Promise的内部状态,一旦状态固定,就不会再改变了

构造函数第一版

const PENDING = "pending";
const FUIFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  #state = PENDING;
  #result = undefined;
  constructor(executor) {
    const resolve = data => {
      if (this.#state !== PENDING) return;
      this.#state = FUIFILLED;
      this.#result = data;
    };
    const reject = reason => {
      if (this.#state !== PENDING) return;
      this.#state = REJECTED;
      this.#result = reason;
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}
  • 这里用三个常量表示MyPromise的内部状态,用ES6类中的私有属性定义状态和结果两个属性,这样外部就访问不到,起到保护作用
  • try...catch用来处理执行器同步函数的错误处理,异步错误是处理不到的
  • resolve和reject两个函数中需要先判断状态,一旦状态发生变化就返回,而且这里是有重复代码的,可以提取出一个#changeState函数

构造函数第二版

const PENDING = "pending";
const FUIFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  #state = PENDING;
  #result = undefined;
  constructor(executor) {
    const resolve = data => {
      this.#changeState(FUIFILLED, data);
    };
    const reject = reason => {
      this.#changeState(REJECTED, reason);
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  /**
   * 状态改变函数
   * @param {string} state 内部状态
   * @param {any} result 内部结果
   * @returns {void} void
   */
  #changeState(state, result) {
    if (this.#state !== PENDING) return;
    this.#state = state;
    this.#result = result;
  }
}

2、MyPromise.prototype.then方法

then函数的签名

then(onFulfilled, onRejected){
    return new MyPromise((resolve,reject)=>{})
  }

其实then方法主要有解决两个问题

  • 第一个是onFulfilled和onRejected两个函数什么时候运行
  • 第二个是返回的新的Promise中的状态什么时候变化即resolve和reject的执行时机

第一个问题:传入的两个回调函数参数onFulfilled和onRejected什么时候执行?

对于第一个问题可以用下图表示:

image.png

/**
   * 执行handlers
   * @returns {void} void
   */
  #run() {
    if (this.#state === PENDING) return;
    // 循环遍历
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FUIFILLED) {
        if (typeof onFulfilled === "function") {
          onFulfilled(this.#result);
        }
      } else {
        if (typeof onRejected === "function") {
          onRejected(this.#result);
        }
      }
    }
  }

  /**
   * 主要记录#handlers、执行#run
   * @param {Function} onFulfilled 成功回调
   * @param {Function} onRejected 失败回调
   * @returns {MyPromise} 返回一个新的MyPromise
   */
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({ onFulfilled, onRejected, resolve, reject });
      this.#run();
    });
  }
  • 如上图所示,then方法所做的事情无非就两件事,第一是记录#handlers数组;第二就是执行#run方法
  • #handlers数组需要在构造函数里定义下,存储一个对象数组
  • #run方法就是在内部状态发生变化时,执行onFulfilled和onRejected两个回调函数

到目前为止,第一个问题大致解决了。

第二个问题:返回新的Promise中的resolve和reject什么时候运行,内部的状态是怎么变化的?

对于第二个问题,有三种情况如下:

  • 传入的onFulfilled和onRejected不是一个函数
  • 传入的回调是一个函数
  • 回调函数的返回结果是一个Promise

当传入的onFulfilled和onRejected不是一个函数

这里要具有穿透性

/**
   * 执行handlers
   * @returns {void} void
   */
  #run() {
    if (this.#state === PENDING) return;
    // 循环遍历
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FUIFILLED) {
        if (typeof onFulfilled === "function") {
          onFulfilled(this.#result);
        } else {
          resolve(this.#result);
        }
      } else {
        if (typeof onRejected === "function") {
          onRejected(this.#result);
        } else {
          reject(this.#result);
        }
      }
    }
  }

当传入的回调是一个函数时

/**
   * 执行handlers
   * @returns {void} void
   */
  #run() {
    if (this.#state === PENDING) return;
    // 循环遍历
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FUIFILLED) {
        if (typeof onFulfilled === "function") {
          const data = onFulfilled(this.#result);
          resolve(data);
        } else {
          resolve(this.#result);
        }
      } else {
        if (typeof onRejected === "function") {
          const data = onRejected(this.#result);
          resolve(data);
        } else {
          reject(this.#result);
        }
      }
    }
  }

当回调函数的返回结果是一个Promise

这里要具有promise互操作性

/**
   * 执行handlers
   * @returns {void} void
   */
  #run() {
    if (this.#state === PENDING) return;
    // 循环遍历
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FUIFILLED) {
        if (typeof onFulfilled === "function") {
          try {
            const data = onFulfilled(this.#result);
            if (this.#isPromiseLike(data)) {
              data.then(resolve, reject);
            } else {
              resolve(data);
            }
          } catch (error) {
            reject(error);
          }
        } else {
          resolve(this.#result);
        }
      } else {
        if (typeof onRejected === "function") {
          try {
            const data = onRejected(this.#result);
            if (this.#isPromiseLike(data)) {
              data.then(resolve, reject);
            } else {
              resolve(data);
            }
          } catch (error) {
            reject(error);
          }
        } else {
          reject(this.#result);
        }
      }
    }
  }

写到这里可以看到有很多重复代码,提取出公共部分,精炼如下:

/**
   * 是否是一个promise
   * @param {any} value 参数
   * @returns {boolean} boolean
   */
  #isPromiseLike(value) {
    return false;
  }

  /**
   * 微任务
   * @param {Function} func 回调函数
   * @returns {void} void
   */
  #runMicrotask(func) {
    setTimeout(func, 0);
  }

  /**
   * 抽取公共函数,遵循DRY原则
   * @param {Function} callback onFulfilled&onRejected回调函数
   * @param {Function} resolve 成功回调
   * @param {Function} reject 失败回调
   * @returns {void} void
   */
  #runOne(callback, resolve, reject) {
    this.#runMicrotask(() => {
      if (typeof callback !== "function") {
        const settled = this.#state === FUIFILLED ? resolve : reject;
        settled(this.#result);
        return;
      }
      try {
        const data = callback(this.#result);
        if (this.#isPromiseLike(data)) {
          data.then(resolve, reject);
        } else {
          resolve(data);
        }
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * 执行handlers
   * @returns {void} void
   */
  #run() {
    if (this.#state === PENDING) return;
    // 循环遍历
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FUIFILLED) {
        this.#runOne(onFulfilled, resolve, reject);
      } else {
        this.#runOne(onRejected, resolve, reject);
      }
    }
  }
  • #runOne函数抽取了#run函数里的公共部分遵循DRY(Don't Repeat Yourself)原则,放在一个微任务队列里
  • #runMicrotask函数是模拟一个微任务
  • #isPromiseLike函数作用是判断一个值是否是一个promise

#isPromiseLike判断一个值是否是一个promise

就是判断该值是否带有一个then方法

/**
   * 是否是一个promise
   * @param {any} value 参数
   * @returns {boolean} boolean
   */
  #isPromiseLike(value) {
    if (value !== null && (typeof value === "object" || typeof value === "function")) {
      return typeof value.then === "function";
    }
    return false;
  }

#runMicrotask模拟一个微任务队列

区分环境:Node和Window

/**
   * 微任务
   * @param {Function} func 回调函数
   * @returns {void} void
   */
  #runMicrotask(func) {
    // 区分环境Node和Window
    if (typeof process === "object" && typeof process.nextTick === "function") {
      process.nextTick(func);
    } else if (typeof MutationObserver === "function") {
      const ob = new MutationObserver();
      const textNode = document.createTextNode("1");
      ob.observe(textNode, { characterData: true });
      textNode.data = "2";
    } else {
      setTimeout(func, 0);
    }
  }

至此,一个完整的PromiseA+规范代码就完成了,ES6中的Promise还有很多实例方法和静态方法,都是在这个then方法基础上搭建出来的,后面再进一步完善一部分方法,完整的代码链接地址gitee

3、补充其他实例方法和静态方法

上述的MyPromise是PromiseA+规范的代码,而ES中的Promise除了构造函数和then方法外,还有其他的方法诸如实例方法catch、finally,静态方法Promise.resolve、Promise.reject、Promise.all、Promise.race和Promise.allSettled等等,下面就一一写出这些函数的代码

Promise.prototype.catch

catch实例方法其实就是then(null, onRejected)的简写,MDN文档也这么介绍的

/**
   * catch实例方法
   * @param {Function} onRejected 拒绝回调
   * @returns {MyPromise} 新的MyPromise
   */
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

Promise.prototype.finally

finally实例方法与then(onFinally,onFinally)类似,但是有两个不同的地方

第一个是finally(onFinally)中onFinally函数不带有任何参数

第二个是返回的Promise的状态是依据上一个Promise的状态,具有穿透性

/**
   * finally实例方法
   * @param {Function} onFinally 回调函数
   * @returns {MyPromise} 新的MyPromise
   */
  finally(onFinally) {
    return this.then(
      data => {
        onFinally();
        return data;
      },
      err => {
        onFinally();
        throw err;
      }
    );
  }

resolve静态方法

这里的resolve方法其实简单就三种情况:

  1. 参数是一个Promise,那么返回这个Promise
  2. 参数是一个thenable对象,那么调用其then方法
  3. 参数除上述两种之外,直接resolve
/**
   * resolve静态方法
   * @param {any} value 参数值
   * @returns {MyPromise} 新的MyPromise
   */
  static resolve(value) {
    if (value instanceof MyPromise) return value;
    let _resolve, _reject;
    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });
    if (p.#isPromiseLike(value)) {
      value.then(_resolve, _reject);
    } else {
      _resolve(value);
    }
    return p;
  }

这里注意:静态方法里是不能使用this,否则会指向全局,所以先new一个Promise,再返回这个Promise

reject静态方法

根据MDN官方文档描述,reject就是返回一个带有拒绝原因的Promise

/**
   * reject静态方法
   * @param {Function} onRejected 拒绝回调
   * @returns {MyPromise} 新的MyPromise
   */
  static reject(onRejected) {
    return new MyPromise((resolve, reject) => {
      reject(onRejected);
    });
  }

all静态方法

all方法接受一个promises可迭代对象作为参数,返回一个新的Promise,其状态与可迭代对象的每一个成员有关,只要全部成员成功了就fulfilled,而有一个失败了就rejected

/**
   * 判断是否是迭代器
   * @param {any} value 参数
   * @returns {boolean} boolean
   */
  #isIterator(value) {
    if (typeof value === "string") {
      return true;
    }
    if (value !== null && (typeof value === "object" || typeof value === "function")) {
      return typeof value[Symbol.iterator] === "function";
    }
    return false;
  }

  /**
   * 静态方法all
   * @param {Iterable} proms 参数
   * @returns {MyPromise} 新的MyPromise
   */
  static all(proms) {
    let _resolve, _reject;
    let count = 0,
      finishNum = 0;
    const result = [];

    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    if (p.#isIterator(proms)) {
      for (const prom of proms) {
        const index = count; // 获取下标
        count++;
        MyPromise.resolve(prom).then(data => {
          result[index] = data; // 将成功的值加入数组中
          finishNum++;
          if (finishNum === count) {
            // 全部完成结束
            _resolve(result);
          }
        }, _reject);
      }

      // 表示空数组
      if (count === 0) {
        _resolve([]);
      }
    } else {
      // 如果iterator不是可迭代对象,则reject
      _reject(`TypeError: ${typeof proms} ${proms} is not iterable (cannot read property Symbol(Symbol.iterator))`);
    }

    return p;
  }

这里注意判断一个值是否是可迭代对象时,字符串是的,其他的对象带有[Symbol.Iterator]属性且是函数的即是

race静态方法

与all不同的是,返回的Promise的状态取决第一个成员状态的结束,无论成功或者失败

/**
   * 静态方法race
   * @param {Iterable} proms 可迭代对象
   * @returns 新的MyPromise
   */
  static race(proms) {
    let _resolve, _reject;

    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    // 谁先结束状态就发生变化
    if (p.#isIterable(proms)) {
      for (const prom of proms) {
        MyPromise.resolve(prom).then(_resolve, _reject);
      }
    } else {
      // 如果iterator不是可迭代对象,则reject
      _reject(`TypeError: ${typeof proms} ${proms} is not iterable (cannot read property Symbol(Symbol.iterator))`);
    }

    return p;
  }

allSettled静态方法

这里根据MDN介绍,输入的所有 promise 都已敲定时(包括传递空的可迭代类型),返回的 promise 将兑现,并带有描述每个 promsie 结果的对象数组。

/**
   * 静态方法allSettled
   * @param {*} proms 可迭代对象
   * @returns 新的MyPromise
   */
  static allSettled(proms) {
    let _resolve, _reject;
    let count = 0,
      total = 0;
    const result = [];

    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    if (p.#isIterable(proms)) {
      for (const prom of proms) {
        const index = count;
        count++;
        MyPromise.resolve(prom)
          .then(value => {
            result[index] = {
              status: "fulfilled",
              value
            };
          })
          .catch(reason => {
            result[index] = {
              status: "rejected",
              reason
            };
          })
          .finally(() => {
            total++;
            if (total === count) {
              // 全部状态都结束
              _resolve(result);
            }
          });
      }

      // 如果可迭代对象是空的则返回空数组
      if (count === 0) {
        _resolve([]);
      }
    } else {
      // 如果iterator不是可迭代对象,则reject
      _reject(`TypeError: ${typeof proms} ${proms} is not iterable (cannot read property Symbol(Symbol.iterator))`);
    }

    return p;
  }

完结撒花~~~