Shane Jix

Promises implementation with ES6 class

create:August 09, 2019  update:April 12, 2022  ☕️☕️☕️ 15 min read

同步链接: https://www.shanejix.com/posts/Promises implementation with ES6 class/

本文参考 Promises/A+ 规范实现 new Promise(),.then()等功能,如不了解 Promise请参考《Promise 必知必会》。更具体的 Promises/A+ 规范见这里

demo

看一个测试代码test.js

let promise1 = new Promise(
  (resolve, reject) => {
    // write your code here

    setTimeout(() => {
      resolve("foo");
    }, 300);
  } /* executor */
);

promise1.then((value) => {
  console.log(value); // expected output: "foo"
});

console.log(promise1); // expected output: [object Promise]

期望的test.js执行阶段

new Promise() => setTimeout() => .then() => console.log(promise1)

输出日志如下

Promise {<pending>}

foo

但是这样不能明显的发现问题,修改test.js如下

let promise1 = new Promise((resolve, reject) => {
  resolve("foo");
});

promise1.then((value) => {
  console.log(value);
});

console.log(promise1);

期望的test.js执行阶段

new Promise() => resolve() => .then() => console.log(promise1)

输出日志如下

Promise {<fulfilled>: 'foo'}

foo

这样问题就很明显了:我们期望的是在 执行rosolve()之前就拿到 promise1.then()中的回调函数 - 这就是Promise实现异步操作的关键之处了

new Promise()

如何实现?

Promise基本的使用

new Promise(
  (resolve, reject) => {
    // write your code here
  } /* executor*/
);

干了什么

1. Promise构造函数执行时会调用 executor 函数

2. resolve 和 reject 两个内部函数作为参数传递给 executor

实现new Promise()构造函数框架如下(标准中没有指定构造函数的具体行为)

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // 2.1. Promise 的状态
    // Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
    this.status = PENDING;

    // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
    this.onFulfilledCallbacks = [];
    // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
    this.onRejectedCallbacks = [];

    // 成功之后的值
    this.value = null;
    // 失败之后的原因
    this.reason = null;

    /**
     * 更改成功后的状态
     * @param {*} value
     */
    const resolve = (value) => {
      // todo
    };

    /**
     * 更改失败后的状态
     * @param {*} reason
     */
    const reject = (reason) => {
      // todo
    };

    // 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
    try {
      // executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

module.exports = Promise;

实现 resolvereject

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // 2.1. Promise 的状态
    // Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
    this.status = PENDING;

    // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
    this.onFulfilledCallbacks = [];
    // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
    this.onRejectedCallbacks = [];

    // 成功之后的值
    this.value = null;
    // 失败之后的原因
    this.reason = null;

    /**
     * 更改成功后的状态
     * @param {*} value
     */
    const resolve = (value) => {
      // 2.1.1. 当 Promise 处于 pending 状态时:
      // 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
      // 2.1.2. 当 Promise 处于 fulfilled 状态时:
      // 2.1.2.1. 不得过渡到任何其他状态。
      // 2.1.2.2. 必须有一个不能改变的值。
      if (this.status === PENDING) {
        // 状态修改为成功
        this.status = FULFILLED;
        // 保存成功之后的值
        this.value = value;
        // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。
        while (this.onFulfilledCallbacks.length) {
          this.onFulfilledCallbacks.shift()(value);
        }
      }
    };

    /**
     * 更改失败后的状态
     * @param {*} reason
     */
    const reject = (reason) => {
      // 2.1.1. 当 Promise 处于 pending 状态时:
      // 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
      // 2.1.3. 当 Promise 处于 rejected 状态时:
      // 2.1.2.1. 不得过渡到任何其他状态。
      // 2.1.2.2. 必须有一个不能改变的值。
      if (this.status === PENDING) {
        // 状态成功为失败
        this.status = REJECTED;
        // 保存失败后的原因
        this.reason = reason;
        // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
        while (this.onRejectedCallbacks.length) {
          this.onRejectedCallbacks.shift()(reason);
        }
      }
    };

    // 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
    try {
      // executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

module.exports = Promise;

这里需要注意一个细节:多个 .then()添加到同一个 promise

let promise = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

这就是 **`onFulfilledCallbacks``onRejectedCallbacks`被处理得为数组得原因**

.then()

根据 demo中的启发:**promise.then()**用来注册在这个 Promise 状态确定后的回调。需要注意的几点

  • 很明显.then()方法需要写在原型链上
  • Promise/A+标准中明确.then()返回一个新对象(详情),Promise实现中几乎都是返回一个新的**Promise**对象

实现**then()****方法框架 **如下

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
   * @param {*} onResolved
   * @param {*} onRejected
   * @returns
   */
  then(onResolved, onRejected) {
    let promise2;

    // 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
    onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (r) => {
            throw r;
          };

    if (this.status === "fulfilled") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }

    if (this.status === "rejected") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }

    if (this.status === "pending") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }
  }
}

后续扩展发现,如下then的结构更灵活:

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
   * @param {*} onResolved
   * @param {*} onRejected
   * @returns
   */
  then(onResolved, onRejected) {
    // 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
    onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (r) => {
            throw r;
          };

    const promise2 = new Promise((resolve, reject) => {
      if (this.status === "fulfilled") {
        // todo
      }

      if (this.status === "rejected") {
        // todo
      }

      if (this.status === "pending") {
        // todo
      }
    });

    return promise2;
  }
}

三种状态下的 Promise都会返回 new Promise()。返回的 promise2的状态如何确定呢?

看个例子

const promise1 = new Promise((resovle, reject) => {
  // ...
});

const promise2 = promise1.then(
  (value) => {
    return 4;
  },
  (reason) => {
    throw new Error("sth went wrong");
  }
);

根据标准,上述代码,promise2的值取决于then里面函数的返回值:

- 如果 promise1 被 resolve 了,promise2 的将被 4 resolve,

- 如果 promise1 被 reject 了,promise2 将被 new Error('sth went wrong') reject,

所以,需要在 then 内部执行 onResolved 或者 onRejected,并根据返回值(标准中记为 x)来确定 promise2 的结果。并且,如果 onResolved/onRejected 返回的是一个 Promise,promise2 将直接取这个 Promise 的结果.

具体实现

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * 2.2. then 方法
   * 一个 promise 必须提供一个 then 方法来访问其当前值或最终值或 rejected 的原因。
   * 一个 promise 的 then 方法接受两个参数:
   * promise.then(onFulfilled, onRejected)
   * @param {*} onFulfilled
   * @param {*} onRejected
   * @returns
   */
  then(onFulfilled, onRejected) {
    // 2.2.1. onFulfilled 和 onRejected 都是可选参数:
    // 2.2.1.1. 如果 onFulfilled 不是一个函数,它必须被忽略。
    // 2.2.7.3. 如果 onFulfilled 不是一个函数且 promise1 为 fulfilled 状态,promise2 必须用和 promise1 一样的值来变为 fulfilled 状态。
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    // 2.2.1. onFulfilled 和 onRejected 都是可选参数:
    // 2.2.1.2. 如果 onRejected 不是一个函数,它必须被忽略。
    // 2.2.7.4. 如果 onRejected 不是一个函数且 promise1 为 rejected 状态,promise2 必须用和 promise1 一样的 reason 来变为 rejected 状态。
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    // 2.2.7. then 必须返回一个 promise
    const promise2 = new Promise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        // 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
        // 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
        setTimeout(() => {
          try {
            // 2.2.2.1. onFulfilled 必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
            // 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
            const x = onFulfilled(this.value);
            // 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
            promiseResolutionHandler(promise2, x, resolve, reject);
          } catch (error) {
            // 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
            reject(error);
          }
        });
      };

      const rejectedMicrotask = () => {
        // 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
        // 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
        setTimeout(() => {
          try {
            // 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
            // 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
            const x = onRejected(this.reason);
            // 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
            promiseResolutionHandler(promise2, x, resolve, reject);
          } catch (error) {
            // 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
            reject(error);
          }
        });
      };

      // 2.2.2. 如果 onFulfilled 是一个函数:
      // 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
      // 2.2.2.2. 它一定不能在 promise 的状态变为 fulfilled 前被调用。
      // 2.2.2.3. 它最多只能被调用一次。
      if (this.status === FULFILLED) {
        // 如果promise1(此处即为this)的状态已经确定并且是fulfilled,调用 resolvedMicrotask
        fulfilledMicrotask();
      }

      // 2.2.3. 如果 onRejected 是一个函数,
      // 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
      // 2.2.3.2. 它一定不能在 promise 的状态变为 rejected 前被调用。
      // 2.2.3.3. 它最多只能被调用一次。
      if (this.status === REJECTED) {
        // 如果promise1(此处即为this)的状态已经确定并且是rejected,调用 rejectedMicrotask
        rejectedMicrotask();
      }

      // 2.2.6. then 可能会被同一个 promise 多次调用。
      if (this.status === PENDING) {
        // 如果当前的Promise还处于pending状态,并不能确定调用onResolved还是onRejected,
        // 只能等到Promise的状态确定后,才能确实如何处理
        // 所以需要把**两种情况**的处理逻辑做为callback放入promise1(此处即this)的回调数组里
        this.onFulfilledCallbacks.push(fulfilledMicrotask);
        this.onRejectedCallbacks.push(rejectedMicrotask);
      }
    });

    return promise2;
  }
}

promiseResolutionHandler集中处理程序

/**
 * 2.3. Promise 处理程序
 * Promise 处理程序是一个将 promise2 和 value 作为输入的抽象操作,将其表示为 [[Resolve]](promise2, x)。
 * 补充说明:这里将 resolve 和 reject 也传入进来,因为后续要根据不同的逻辑对 promise2 执行 fulfill 或 reject 操作。
 * @param {*} promise2
 * @param {*} x
 * @param {*} resolve
 * @param {*} reject
 * @returns
 */
function promiseResolutionHandler(promise2, x, resolve, reject) {
  // 2.3.1. 如果 promise2 和 x 引用的是同一个对象,promise2 将以一个 TypeError 作为 reason 来进行 reject。
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise"));
  }

  /**
   
  // 与 2.3.3 有重叠部分

  // 2.3.2. 如果 x 是一个 Promise,根据它的状态:
  if (x instanceof Promise) {
    // 2.3.2.1. 如果 x 的状态为 pending,Promise 必须保持 pending 状态直到 x 的状态变为 fulfilled 或 rejected。
    if (x.state === "pending") {
      x.then(
        (value) => {
          promiseResolutionHandler(promise2, value, resolve, reject);
        },
        reject
      );
    } else if (x.state === "fulfilled") {
      // 2.3.2.2. 如果 x 的状态为 fulfilled,那么 promise2 也用同样的值来执行 fulfill 操作。
      resolve(x.data);
    } else if (x.state === "rejected") {
      // 2.3.2.3. 如果 x 的状态为 rejected,那么 promise2 也用同样的 reason 来执行 reject 操作。
      reject(x.data);
    }
    return;
  }

  */

  // 2.3.3. 除此之外,如果 x 是一个对象或者函数,
  if (typeof x === "object" || typeof x === "function") {
    // 如果 x 是 null,直接 resolve
    if (x === null) {
      return resolve(x);
    }

    // 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
    let isCalled = false;

    try {
      // 2.3.3.1. 声明一个 then 变量来保存 then
      let then = x.then;
      // 2.3.3.3. 如果 then 是一个函数,将 x 作为 this 来调用它,第一个参数为 resolvePromise,第二个参数为 rejectPromise,其中:
      if (typeof then === "function") {
        try {
          then.call(
            x,
            // 2.3.3.3.1. 假设 resolvePromise 使用一个名为 y 的值来调用,运行 Promise 处理程序 [[Resolve]](promise, y)。
            function resolvePromise(y) {
              // 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
              if (isCalled) return;
              isCalled = true;
              promiseResolutionHandler(promise2, y, resolve, reject);
            },
            // 2.3.3.3.2. 假设 rejectPromise 使用一个名为 r 的 reason 来调用,则用 r 作为 reason 对 promise2 执行 reject 操作。
            function rejectPromise(r) {
              if (isCalled) return;
              isCalled = true;
              reject(r);
            }
          );
        } catch (error) {
          // 2.3.3.3.4. 如果调用 then 时抛出一个异常 e,
          // 2.3.3.3.4.1. 如果 resolvePromise 或 rejectPromise 已经被调用过了,则忽略异常。
          if (isCalled) return;

          // 2.3.3.3.4.2. 否则,使用 e 作为 reason 对 promise2 执行 reject 操作。
          reject(error);
        }
      } else {
        // 2.3.3.4. 如果 then 不是一个函数,使用 x 作为值对 promise2 执行 fulfill 操作。
        resolve(x);
      }
    } catch (error) {
      // 2.3.3.2. 如果检索 x.then 的结果抛出异常 e,使用 e 作为 reason 对 promise2 执行 reject 操作。
      return reject(error);
    }
  } else {
    // 2.3.4. 如果 x 不是一个对象或者函数,使用 x 作为值对 promise2 执行 fulfill 操作。
    resolve(x);
  }
}

至此就完成了then()的实现,需要引起注意的地方:

注意点一:可以看出同步任务不涉及 callback 的存储,异步任务会先进入宏任务队列,会在 JS 主栈空闲时执行存储的 callback, 核心实现其实就是——发布订阅模式

注意点二:链式调用的核心就是.then() 返回一个新的 Promise

注意点三:**Promise**值穿透

// 片段一
new Promise((resolve) => resolve(8))
  .then()
  .catch()
  .then((value) => {
    alert(value);
  });

// 片段二
new Promise((resolve) => resolve(8))
  .then((value) => {
    return value;
  })
  .catch((reason) => {
    throw reason;
  })
  .then((value) => {
    alert(value);
  });

片段一和片段二效果应该一样,如果**then()**的实参留空且让值可以穿透到后面,只需要给**then()**的两个参数设定默认值即可

onResolved =
  typeof onResolved === "function"
    ? onResolved
    : (value) => {
        return value;
      };

onRejected =
  typeof onRejected === "function"
    ? onRejected
    : (reason) => {
        throw reason;
      };

注意点四:**thenable 的核心逻辑 **参考promiseResolutionHandler集中处理程序

.catch()

其实就是.then(null,()=>()}

class Promise {
  // ...

  /**
   * catch方法
   * @param {*} onRejected
   * @returns
   */
  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

Promise.all()

class Promise {
  // ...

  /**
   * Promise.all()方法
   * @param {*} promiseArr
   * @returns
   */
  static all(promiseArr = []) {
    return new Promise((resolve, reject) => {
      // 记录成功的数量
      let index = 0;
      // 记录成功的结果
      let result = [];

      for (let i = 0; i < promiseArr.length; i++) {
        promiseArr[i].then((val) => {
          index++;
          result[i] = val; // 保证结果的顺序和数组顺序一致
          if (index === promiseArr.length) {
            resolve(result);
          }
        }, reject);
      }
    });
  }
}

小结

社区有很多实现,包括一些三方库的实现,还有其他语言基于 Promises/A+ 的实现:https://promisesaplus.com/implementations

完整版实现:https://github.com/shanejix/front-end-playground/blob/master/javascript/implement-promise/promise.js

测试结果: image.png

new Promise()处理同步任务和异步任务有所区别:同步任务会立即 resolve()掉并修改 当前 promise 的状态。异步任务会预先存储 callback(订阅事件),然后等待时机resolve() 掉 当前 promise ,核心思想就是 **发布订阅模式 **。

.then()默认返回一个新的 promise :这是 promise 实现 **链式调用 **的核心

references

作者:shanejix 出处:https://www.shanejix.com/posts/Promises implementation with ES6 class/ 版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。 声明:转载请注明出处!

Edit on GitHubDiscuss on GitHub


Shane Jix

Personal blog by Shane Jix. I explain with words and code.

LinksTools
© 2019 - 2022, Built withGatsby