关于 Promise 的一些笔记

171 阅读9分钟

Promise

1-3 纯属废话 = =

1. 术语

  1. promise 是一个具有 then 方法的对象或者函数。
  2. thenable 是一个定义了 then 方法的对象或者函数。
  3. value 是一个合法的 JavaScript 值(包括 undefined、thenable 或 promise)。
  4. exception 是一个使用 throw 语句抛出的值。
  5. reason 是一个值,其表明了 promise 被拒绝的原因。

2. 要求

2.1 Promise 的状态

Promise 必须处于以下三种状态之一:pending, fulfilled, or rejected.

  1. when pending -> fulfilled or rejected
  2. when fulfilled 不能变更状态,并且具有一个值,其不能被改变。
  3. when rejected 不能变更状态,并且具有一个被拒绝的理由,其不能被改变。

2.2 then 方法

承诺必须提供 then 方法来访问其当前或最终值或原因。

一个 promise 的then方法 接受两个参数

promise.then(onFulfilled, onRejected);

2.2.1 onFulfilled 和 onRejected 都是可选参数

  1. 如果 onFulfilled 不是一个函数,那么它将被忽略。(替换为一个默认函数 (value) => value)
  2. 如果 onRejected 不是一个函数,那么它将被忽略。(替换为一个默认函数 (reason) => { throw reason; } )

2.2.2 如果 onFulfilled 是一个函数

  1. 必须在 promise 完成后调用它,promise 的值作为它的第一个参数。
  2. 在 promise 完成之前不能调用它。
  3. 它不能被多次调用。

2.2.3 如果 onRejected 是一个函数

  1. 必须在 promise 被拒绝后调用它,promise 的原因作为它的第一个参数。
  2. 在 promise 被拒绝之前不能调用它。
  3. 它不能被多次调用。

2.2.4 在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected

2.2.5 onFulfilled 和 onRejected 必须作为函数调用(即没有这个值)。

2.2.6 then 可以在同一个 promise 上多次调用。

  1. 如果 promise 完成时,所有相应的 onFulfilled 回调必须按照它们对 then 的原始调用的顺序执行。
  2. 如果 promise 被拒绝,则所有相应的 onRejected 回调必须按照它们对 then 的原始调用的顺序执行。

2.2.7 then 必须返回一个 新的 promise

promise2 = promise1.then(onFulfilled, onRejected);
  1. 如果 onFulfilled 或 onRejected 返回值 x,则运行 Promise Resolution Procedure [[Resolve]](promise2, x)。
  2. 如果 onFulfilled 或 onRejected 抛出异常 e,promise2 必须以 e 作为原因被拒绝。
  3. 如果 onFulfilled 不是函数并且 promise1 已实现,则 promise2 必须以与 promise1 相同的值实现。
  4. 如果 onRejected 不是函数并且 promise1 被拒绝,则 promise2 必须以与 promise1 相同的原因被拒绝。

2.3 The Promise Resolution Procedure

承诺解析过程是一个抽象操作,将承诺和值作为输入,我们将其表示为 [[Resolve]](promise, x)。 如果 x 是一个 thenable,它会尝试让 promise 采用 x 的状态,假设 x 的行为至少有点像 promise。 否则,它以值 x 履行承诺。对 thenables 的这种处理允许 promise 实现互操作,只要它们公开符合 PromisesA+ 的 then 方法。 它还允许 PromisesA+ 实现使用合理的 then 方法“同化”不一致的实现。

要运行 [[Resolve]](promise, x),请执行以下步骤:

2.3.1. 如果 promise 和 x 引用同一个对象,则以 TypeError 作为原因拒绝 promise。

2.3.2 如果 x 是一个 promise,采用它的状态:

  1. 如果 x 是 pending,promise 必须保持 pending 直到 x 被完成或拒绝。
  2. 如果当 x 被完成时,用相同的值实现承诺。
  3. 如果当 x 被拒绝时,以同样的理由拒绝承诺。

2.3.3 否则,如果 x 是一个对象或函数,

  1. 设 then 为 x.then。
  2. 如果检索属性 x.then 导致抛出异常 e,则以 e 为原因拒绝 promise。
  3. 如果 then 是一个函数,则使用 x 作为 this 调用它,第一个参数是 resolvePromise,第二个参数是 rejectPromise,其中:
    1. 如果使用值 y 调用 resolvePromise 时,则运行 [[Resolve]](promise, y)。
    2. 如果使用原因 r 调用 rejectPromise,则使用 r 拒绝 promise。
    3. 如果同时调用了 resolvePromise 和 rejectPromise,或者对同一个参数进行了多次调用,则第一个调用优先,任何进一步的调用都将被忽略。
    4. 如果调用后抛出异常 e,
      1. 如果调用了 resolvePromise 或 rejectPromise ,则忽略它。
      2. 否则,以 e 为理由拒绝 promise。
  4. 如果 then 不是函数,则用 x 实现 promise。

2.3.4 如果 x 不是对象或函数,则用 x 实现 promise。

如果一个 promise 用一个参与循环 thenable 链的 thenable 解析, 这样[[Resolve]](promise, thenable)的递归性质最终会导致[[Resolve]](promise, thenable) 被再次调用,如下上述算法会导致无限递归。 鼓励但不要求实现以检测此类递归并拒绝以信息丰富的 TypeError 作为原因的承诺。

3. 笔记

  1. 这里的平台代码是指引擎、环境和 promise 实现代码。在实践中,这个要求确保 onFulfilled 和 onRejected 异步执行,在调用 then 的事件循环之后,并使用新的堆栈。 这可以通过“宏任务”机制(例如setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。 由于 promise 实现被认为是平台代码,它本身可能包含一个任务调度队列或“trampoline”,在其中调用处理程序。

  2. 也就是说,在严格模式下,这在它们内部是未定义的;在草率模式下,它将是全局对象。

  3. 实现可以允许 promise2 === promise1,前提是实现满足所有要求。每个实现都应该记录它是否可以产生 promise2 === promise1 以及在什么条件下。

  4. 通常,只有当 x 来自当前实现时,才会知道 x 是一个真正的承诺。该条款允许使用特定于实现的手段来采用已知符合承诺的状态。

  5. 这个首先存储对 x.then 的引用,然后测试该引用,然后调用该引用的过程避免了对 x.then 属性的多次访问。这些预防措施对于确保访问器属性的一致性很重要,访问器属性的值可能会在检索之间发生变化。

  6. 实现不应对 thenable 链的深度设置任意限制,并假设超出该任意限制递归将是无限的。只有真正的循环才会导致 TypeError;如果遇到无限的不同 thenable 链,则永远递归是正确的行为。

4. 易错点

  1. Promise.then 返回的是一个 新的 Promise 对象。
  2. Promise.all | Promise.race | Promise.allSettled | Promise.any 当且仅当传入的可迭代对象为空时为同步。
  3. Promise.all | Promise.race | Promise.allSettled | Promise.any 参数的均是 可迭代对象(不仅仅只是数组)。
  4. Promise.race 传入的是空的可迭代对象的话, 会一直等待。

5. 源码实现

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

const isObject = (obj) => obj !== null && typeof obj === "object";
const isFunction = (param) => typeof param === "function";

class Promise {
  constructor(executor) {
    if (typeof executor !== "function") {
      throw new TypeError("executor is not a function");
    }
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (value instanceof Promise) {
        // 2.3.2
        // 递归解析resolve中的参数 直到不是promise对象
        value.then(resolve, reject);
        return;
      }
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED;
        // 如无以下方法,则无法处理异步操作
        this.onResolvedCallbacks.forEach((callback) => callback(value));
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
        // 如无以下方法,则无法处理异步操作
        this.onRejectedCallbacks.forEach((callback) => callback(reason));
      }
    };

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

  static resolve(value) {
    return new Promise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  }

  then(onFulfilled, onRejected) {
    // 2.2.1.1
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;
    // 2.2.1.2
    onRejected = isFunction(onRejected)
      ? onRejected
      : (reason) => {
          throw reason;
        };

    let promise2;
    return (promise2 = new Promise((resolve, reject) => {
      switch (this.status) {
        case FULFILLED:
          // 没有用 queueMicrotask 则 promise2 不存在 (new对象的执行过程)
          queueMicrotask(() => {
            try {
              const x = onFulfilled(this.value); // 2.2.7.1
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              // 用了 queueMicrotask 则不会走类里面的 try catch
              reject(error); // 2.2.7.2
            }
          }); // 2.2.4 | 2.2.2.2
          break;
        case REJECTED:
          queueMicrotask(() => {
            try {
              const x = onRejected(this.reason); // 2.2.7.1
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              // 用了 queueMicrotask 则不会走类里面的 try catch
              reject(error); // 2.2.7.2
            }
          }); // 2.2.4 | 2.2.2.2
          break;
        case PENDING:
          this.onResolvedCallbacks.push(() => {
            queueMicrotask(() => {
              try {
                const x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
              } catch (error) {
                reject(error);
              }
            });
          });
          this.onRejectedCallbacks.push(() => {
            queueMicrotask(() => {
              try {
                const x = onRejected(this.value); // 2.2.7.1
                resolvePromise(promise2, x, resolve, reject);
              } catch (error) {
                // 用了 queueMicrotask 则不会走类里面的 try catch
                reject(error); // 2.2.7.2
              }
            }); // 2.2.4 | 2.2.2.2
          });
      }
    })); // 2.2.7
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  /**
   * finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。
   * 这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
   * 这避免了同样的语句需要在then()和catch()中各写一次的情况。
   * @param onFinally
   * @returns {Promise}
   */
  finally(onFinally) {
    return this.then(
      (value) => Promise.resolve(onFinally()).then(() => value),
      (reason) =>
        Promise.resolve(onFinally()).then(() => {
          throw reason;
        })
    );
  }

  /**
   * @param values 一个可迭代对象,如 Array 或 String。
   * @returns 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
   如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved) Promise。
   注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise。
   其它情况下返回一个处理中(pending)的Promise。
   这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。
   返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。
   */
  static all(values) {
    return new Promise((resolve, reject) => {
      values = [...values];

      if (values.length === 0) {
        return resolve(values);
      }

      let counter = 0;
      const result = [];
      values.forEach((val, index) => {
        Promise.resolve(val)
          .then((res) => {
            result[index] = res;
            // 易错点
            // 需要一个计数器来确保实际完成的数量
            // for example: 可能 values 的最后一项是最先完成的, 所以 如果用 result.length 来判断的话,会导致误以为全部完成的情况发生
            counter++;
            if (counter === values.length) {
              resolve(result);
            }
          })
          .catch(reject);
      });
    });
  }

  /**
   * 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
   * @param values
   * @returns {Promise}
   */
  static allSettled(values) {
    return new Promise((resolve, reject) => {
      values = [...values];
      const result = [];
      let counter = 0;
      if (values.length === 0) {
        return resolve(values);
      }
      values.forEach((value, index) => {
        Promise.resolve(value)
          .then((val) => {
            result[index] = {
              status: FULFILLED,
              value: val,
            };
            counter++;
            if (counter === values.length) {
              resolve(result);
            }
          })
          .catch((reason) => {
            result[index] = {
              status: REJECTED,
              reason,
            };
            counter++;
            if (counter === values.length) {
              resolve(result);
            }
          });
      });
    });
  }

  /**
   * 一旦迭代器中的某个promise解决或拒绝,返回的 promise 就会解决或拒绝。
   * @param values 只要是一个可迭代的对象即可
   * 目前所有的内置可迭代对象如下:String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。
   * @returns {Promise}
   */
  static race(values) {
    return new Promise((resolve) => {
      values = [...values];
      values.forEach((value) => {
        resolve(value);
      });
    });
  }

  /**
   * 只要迭代器中的一个 promise 成功,就返回那个已经成功的 promise,
   * 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例
   * @param values 只要是一个可迭代的对象即可
   * 目前所有的内置可迭代对象如下:String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。
   * @returns {Promise}
   */
  static any(values) {
    return new Promise((resolve, reject) => {
      values = [...values];
      const reasons = [];
      let count = 0;
      values.forEach((value, index) => {
        Promise.resolve(value)
          .then(resolve)
          .catch((error) => {
            reasons[index] = error;
            count++;
            if (count === values.length) {
              reject(new AggregateError(reasons));
            }
          });
      });
    });
  }
}

const resolvePromise = (promise2, x, resolve, reject) => {
  let isCalled = false;
  if (promise2 === x) {
    reject(new TypeError("Chaining cycle detected for promise")); // 2.3.1
    return;
  }
  // 为了能兼容其他符合 PromiseA+标准的promise 如: bluebird q es6-promise
  if (x instanceof Promise) {
    // 2.3.2 可以不写,与 2.3.3 一个逻辑
    // 这边需要加深印象
    queueMicrotask(() => {
      x.then(
        (y) => {
          resolvePromise(promise2, y, resolve, reject);
        },
        (e) => {
          reject(e);
        }
      ); // 2.3.2.1
    });
  } else if (isObject(x) || isFunction(x)) {
    // 2.3.3
    try {
      let then = x.then; // 2.3.3.1
      if (isFunction(then)) {
        queueMicrotask(() => {
          then.call(
            x,
            (y) => {
              if (isCalled) return;
              isCalled = true;
              resolvePromise(promise2, y, resolve, reject); // 2.3.3.3.1
            },
            (e) => {
              if (isCalled) return;
              isCalled = true;
              reject(e); // 2.3.3.3.3
            }
          ); // 2.3.3.3
        });
      } else {
        resolve(x); // 2.3.3.4
      }
    } catch (error) {
      if (isCalled) return; // 2.3.3.3.4.1
      isCalled = true;
      reject(error); // 2.3.3.2 |  2.3.3.3.4.1
    }
  } else {
    resolve(x); // 2.3.4
  }
};

6. 习题

Promise.resolve()
  .then(() => {
    console.log(0);
    setTimeout(() => {
      console.log("宏任务");
    }, 0);
    return Promise.resolve(4);
  })
  .then((res) => {
    console.log(res);
  });

Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(5);
  })
  .then(() => {
    console.log(6);
  })
  .then(() => {
    console.log(7);
  });