手写符合 Promises/A+ Spec 的 Promise

Last updated:

我一直想知道 Promise 究竟是怎么工作的,于是我去阅读了 Promises/A+ Spec,并实现了一个通过 Promises/A+ Tests 的 Polyfill —— Inshin

Inshin 的读音([ˈɪnʃɪn])近似粤语的承诺(应承)一词。

这在当初只花费了几天时间,却让我受益至今。

# 源码

我将源码板书在此,也托管在 这个仓库

这种排版松散的编码风格来自 three.js,它很漂亮,我喜欢它,并且还专门定义了一套 ESLint 规则,直到我向 Prettier 屈服。

const PENDING_STATE = 'pending';
const FULFILLED_STATE = 'fulfilled';
const REJECTED_STATE = 'rejected';

/**
 * 通过 Promises/A+ 测试的 Promise polyfill。
 * @param   { Function } execute - 该入参等同于原生Promise的入参。
 * @returns { Object } - Inshin实例,等同于Promise实例。
 */
function Inshin(execute) {
  /* Inshin实例的内部数据。 */
  const self = this;

  self._state = PENDING_STATE;

  self._fulfilled_value = undefined;
  self._rejected_value = undefined;

  self._fulfilled_events = [];
  self._rejected_events = [];

  /*  */
  execute(resolve, reject);

  /**
   * resolve函数,用于敲定Inshin实例。
   * @param   { * } fulfilled_value Inshin实例的fulfilled值,代表敲定后的值。
   * @returns { undefined } - undefined。
   */
  function resolve(fulfilled_value) {
    if (self._state !== PENDING_STATE) return;

    self._state = FULFILLED_STATE;
    self._fulfilled_value = fulfilled_value;

    self._fulfilled_events.forEach(function (handleThen) {
      const microtask = (_) => handleThen(self._fulfilled_value);

      globalThis.queueMicrotask(microtask);
    });
  }

  /**
   * rejecte函数,用于拒绝Inshin实例。
   * @param   { * } rejected_value - Inshin实例的rejected值,代表被拒绝的原因。
   * @returns { undefined } - undefined。
   */
  function reject(rejected_value) {
    if (self._state !== PENDING_STATE) return;

    self._state = REJECTED_STATE;
    self._rejected_value = rejected_value;

    self._rejected_events.forEach(function (handleThen) {
      const microtask = (_) => handleThen(self._rejected_value);

      globalThis.queueMicrotask(microtask);
    });
  }
}

/**
 * then方法。
 * @param { Function } handleFulfilled - Inshin实例的fulfilled订阅函数。
 * @param { Function } handleRejected  - Inshin实例的rejected订阅函数。
 * @returns { Object } - 一个新的Inshin实例或一个新的thenable对象。
 */
Inshin.prototype.then = function then(handleFulfilled, handleRejected) {
  /*  */
  let inshinResolve;
  let inshinReject;

  const inshin = new Inshin((resolve, reject) => {
    inshinResolve = resolve;
    inshinReject = reject;
  });

  /*  */
  this._fulfilled_events.push(handleFulfilledAndInshin);

  if (this._state === FULFILLED_STATE) {
    const microtask = (_) => handleFulfilledAndInshin(this._fulfilled_value);

    globalThis.queueMicrotask(microtask);
  }

  /**
   * handleFulfilled函数的代理者。
   * @param { * } fulfilled_value - Inshin实例的fulfilled值。
   * @returns { undefined } - undefined。
   */
  function handleFulfilledAndInshin(fulfilled_value) {
    if (typeof handleFulfilled === 'function') {
      let x;

      try {
        x = handleFulfilled(fulfilled_value);
      } catch (error) {
        inshinReject(error);
      }

      inshinResolutionProcedure(inshin, x);
    } else {
      inshinResolve(fulfilled_value);
    }
  }

  /* */
  this._rejected_events.push(handleRejectedAndInshin);

  if (this._state === REJECTED_STATE) {
    const microtask = (_) => handleRejectedAndInshin(this._rejected_value);

    globalThis.queueMicrotask(microtask);
  }

  /**
   * handleRejected函数的代理者
   * @param { * } rejected_value - Inshin实例的rejected值。
   * @returns { undefined } - undefined。
   */
  function handleRejectedAndInshin(rejected_value) {
    if (typeof handleRejected === 'function') {
      let x;

      try {
        x = handleRejected(rejected_value);
      } catch (error) {
        inshinReject(error);
      }

      inshinResolutionProcedure(inshin, x);
    } else {
      inshinReject(rejected_value);
    }
  }

  /* [[Resolve]]( inshin, x ) */
  /**
   * The Promise Resolution Procedure,详见规范的2.3。
   * @param { Object } inshin - Inshin实例或thenable对象。
   * @param { * } x - handleFulfilled函数或handleRejected函数的返回值。
   * @returns { undefined } - undefine。
   */
  function inshinResolutionProcedure(inshin, x) {
    if (x === null) {
      inshinResolve(x);

      return;
    }

    if (typeof x !== 'object' && typeof x !== 'function') {
      inshinResolve(x);

      return;
    }

    if (x === inshin) {
      inshinReject(new TypeError('Chaining cycle detected for promise'));

      return;
    }

    if (typeof x === 'object' || typeof x === 'function') {
      let then;

      try {
        then = x.then;
      } catch (error) {
        inshinReject(error);
      }

      if (typeof then === 'function') {
        let is_finish = false;

        const resolve = function resolve(y) {
          if (is_finish) return;

          is_finish = true;

          inshinResolutionProcedure(inshin, y);
        };
        const reject = function reject(r) {
          if (is_finish) return;

          is_finish = true;

          inshinReject(r);
        };

        try {
          then.call(x, resolve, reject);
        } catch (error) {
          if (!is_finish) inshinReject(error);
        }

        return;
      }

      inshinResolve(x);
    }
  }

  /*  */
  return inshin;
};

module.exports = Inshin;

# 测试

如果你想知道自己的实现是否符合 Promises/A+ Spec,那么就用 Promises/A+ Tests,这是 Promises/A+ 的测试套件。

如果你读不懂 Promises/A+ Tests 的用法,那么请参考 Inshin 的 test.js 文件

# 使用

如果你需要 Promise 的 Polyfill,那么请使用社区的实现,比如 core-js,而不要使用 Inshin,因为前者更加健壮。