返回

掌握 Promise 的奥秘:从头构建一个符合 PromiseA+ 规范的 Promise 实现

前端

在 JavaScript 的广阔世界中,Promise 脱颖而出,成为一种处理异步操作的优雅而强大的工具。它允许开发人员以结构化且可控的方式管理异步代码,简化复杂任务并提升代码的可读性和可维护性。

本文将踏上一段引人入胜的旅程,从头开始构建一个符合 PromiseA+ 规范的 Promise 实现。通过深入探讨 Promise 的内部机制,我们将揭开其魔力,探索其巧妙的设计和广泛的应用。

揭开 Promise 的面纱

Promise 本质上是一个对象,它代表着异步操作的最终结果。它具有三个状态:待定(pending)、已完成(fulfilled)和已拒绝(rejected)。

当一个 Promise 被创建时,它处于待定状态。一旦异步操作完成,它将根据操作的结果转换为已完成或已拒绝状态。已完成的 Promise 包含一个代表操作结果的值,而已拒绝的 Promise 包含一个错误对象,指示操作失败的原因。

编写 myPromise 实现

为了构建一个符合 PromiseA+ 规范的 Promise 实现,我们需要定义一系列方法:

  • constructor:创建 Promise 实例。
  • resolve:将 Promise 状态转换为已完成,并提供一个结果值。
  • reject:将 Promise 状态转换为已拒绝,并提供一个错误对象。
  • then:创建一个新的 Promise,它在当前 Promise 完成或拒绝后执行。
  • catch:创建一个新的 Promise,它在当前 Promise 拒绝后执行。
  • finally:创建一个新的 Promise,无论当前 Promise 完成或拒绝,它都会执行。
  • race:创建一个新的 Promise,它接收一个 Promise 数组,并在第一个 Promise 完成或拒绝时完成或拒绝。
  • all:创建一个新的 Promise,它接收一个 Promise 数组,并在所有 Promise 完成或拒绝时完成或拒绝。
  • allSettled:创建一个新的 Promise,它接收一个 Promise 数组,并在所有 Promise 完成或拒绝后完成,无论结果如何。

满足 PromiseA+ 规范

PromiseA+ 规范定义了 Promise 应该如何的行为。我们的 myPromise 实现必须满足以下要求:

  • 必须有一个 then 方法,接受两个回调函数作为参数。
  • 必须有一个 catch 方法,接受一个回调函数作为参数。
  • 必须有一个 finally 方法,接受一个回调函数作为参数。
  • 必须有一个 race 方法,接受一个 Promise 数组作为参数。
  • 必须有一个 all 方法,接受一个 Promise 数组作为参数。
  • 必须有一个 allSettled 方法,接受一个 Promise 数组作为参数。
  • then 方法必须返回一个新的 Promise。
  • catch 方法必须返回一个新的 Promise。
  • finally 方法必须返回一个新的 Promise。
  • race 方法必须返回一个新的 Promise。
  • all 方法必须返回一个新的 Promise。
  • allSettled 方法必须返回一个新的 Promise。

示例代码

以下是一个符合 PromiseA+ 规范的 myPromise 实现示例:

class myPromise {
  constructor(executor) {
    this.state = "pending";
    this.result = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state !== "pending") return;
      this.state = "fulfilled";
      this.result = value;
      this.onFulfilledCallbacks.forEach((callback) => callback(value));
    };

    const reject = (error) => {
      if (this.state !== "pending") return;
      this.state = "rejected";
      this.result = error;
      this.onRejectedCallbacks.forEach((callback) => callback(error));
    };

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

  then(onFulfilled, onRejected) {
    return new myPromise((resolve, reject) => {
      if (this.state === "fulfilled") {
        try {
          const result = onFulfilled(this.result);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      } else if (this.state === "rejected") {
        try {
          const result = onRejected(this.result);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      } else {
        this.onFulfilledCallbacks.push(() => {
          try {
            const result = onFulfilled(this.result);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {
            const result = onRejected(this.result);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      }
    });
  }

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

  finally(onFinally) {
    return this.then(
      (result) => {
        onFinally();
        return result;
      },
      (error) => {
        onFinally();
        throw error;
      }
    );
  }

  static race(promises) {
    return new myPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then((result) => resolve(result)).catch((error) => reject(error));
      });
    });
  }

  static all(promises) {
    return new myPromise((resolve, reject) => {
      let results = [];
      let completedCount = 0;

      promises.forEach((promise, index) => {
        promise
          .then((result) => {
            results[index] = result;
            completedCount++;

            if (completedCount === promises.length) {
              resolve(results);
            }
          })
          .catch((error) => reject(error));
      });
    });
  }

  static allSettled(promises) {
    return new myPromise((resolve) => {
      let results = [];
      let completedCount = 0;

      promises.forEach((promise, index) => {
        promise
          .then((result) => {
            results[index] = { status: "fulfilled", value: result };
            completedCount++;

            if (completedCount === promises.length) {
              resolve(results);
            }
          })
          .catch((error) => {
            results[index] = { status: "rejected", reason: error };
            completedCount++;

            if (completedCount === promises.length) {
              resolve(results);
            }
          });
      });
    });
  }
}

结论

通过构建一个符合 PromiseA+ 规范的 Promise 实现,我们深入了解了 Promise 的工作原理。我们不仅掌握了 Promise 的基础知识,还获得了实践经验,从而巩固了我们的理解。

从解决回调地狱到管理复杂的异步操作,Promise 已经成为现代 JavaScript 开发中不可或缺的工具。通过熟练掌握 Promise 的奥秘,我们可以编写更简洁、更可读和更可维护的代码。

此外,这种亲自动手构建 Promise 实现的过程提升了我们对异步编程的理解,使我们能够自信地应对各种异步场景,打造更高效、更健壮的应用程序。