返回

手写 Promise 全功能实现,超越原生 API

前端

手写 Promise,超越原生 API

异步编程的利器

在 JavaScript 的世界中,Promise 就像一把锋利的宝剑,助你轻松驾驭异步编程的汪洋大海。原生 Promise API 固然强大,但偶尔也会捉襟见肘,无法满足一些特定的需求。为了突破瓶颈,我们不妨亲自动手,打造一个更强大的 Promise 实现,探索异步编程的更多可能。

Promise 的本质

Promise 的本质很简单,它就像一个处于等待、已解决或已拒绝三种状态之中的容器。当一个异步操作启动时,它将被 Promise 收入囊中,静静等待结果。若操作成功,Promise 就会将其结果打包带走,化身已解决状态。反之,若操作失败,Promise 也会将失败原因收录其中,成为已拒绝状态。

手写 Promise 的基本实现

想要打造自己的 Promise,我们首先要搭建它的基本框架。一个 Promise 构造函数和一个 then 方法足以满足基本的异步处理需求。

class Promise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    // resolve 和 reject 函数
    const resolve = (value) => {
      // 确保只执行一次
      if (this.state !== 'pending') return;
      this.state = 'resolved';
      this.value = value;
      this.onFulfilledCallbacks.forEach((callback) => callback(value));
    };

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

    // 执行 executor 函数
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    // 返回一个新的 Promise
    return new Promise((resolve, reject) => {
      if (this.state === 'resolved') {
        // 使用 setTimeout 异步执行回调
        setTimeout(() => {
          try {
            // 获取 onFulfilled 的返回值
            const value = onFulfilled(this.value);
            resolve(value);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            // 获取 onRejected 的返回值
            const reason = onRejected(this.reason);
            reject(reason);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        // 等待状态,将回调加入队列
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const value = onFulfilled(this.value);
              resolve(value);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const reason = onRejected(this.reason);
              reject(reason);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
  }
}

完善 Promise 的功能

为了让我们的 Promise 更加强大,我们可以继续添加更多实用的方法。

  • All 方法: 处理多个 Promise,直到所有 Promise 都已解决或有一个 Promise 被拒绝。
Promise.all = (promises) => {
  // 返回一个新的 Promise
  return new Promise((resolve, reject) => {
    const results = [];
    let pendingCount = promises.length;

    // 遍历每个 Promise
    promises.forEach((promise, index) => {
      promise.then((value) => {
        results[index] = value;
        pendingCount--;
        if (pendingCount === 0) {
          resolve(results);
        }
      }, reject);
    });
  });
};
  • AllSettled 方法: 处理多个 Promise,无论它们是解决还是拒绝,都返回一个已解决的状态。
Promise.allSettled = (promises) => {
  // 返回一个新的 Promise
  return new Promise((resolve) => {
    const results = [];
    let pendingCount = promises.length;

    // 遍历每个 Promise
    promises.forEach((promise, index) => {
      promise.then(
        (value) => {
          results[index] = { status: 'fulfilled', value };
          pendingCount--;
          if (pendingCount === 0) {
            resolve(results);
          }
        },
        (reason) => {
          results[index] = { status: 'rejected', reason };
          pendingCount--;
          if (pendingCount === 0) {
            resolve(results);
          }
        }
      );
    });
  });
};
  • Any 方法: 处理多个 Promise,只要有一个 Promise 解决就返回该解决的结果,否则返回拒绝的结果。
Promise.any = (promises) => {
  // 返回一个新的 Promise
  return new Promise((resolve, reject) => {
    let rejectedCount = 0;

    // 遍历每个 Promise
    promises.forEach((promise) => {
      promise.then(resolve, (reason) => {
        rejectedCount++;
        if (rejectedCount === promises.length) {
          reject(new AggregateError('All promises were rejected'));
        }
      });
    });
  });
};
  • Race 方法: 处理多个 Promise,只要有一个 Promise 解决或拒绝,就返回该结果。
Promise.race = (promises) => {
  // 返回一个新的 Promise
  return new Promise((resolve, reject) => {
    // 遍历每个 Promise
    promises.forEach((promise) => {
      promise.then(resolve, reject);
    });
  });
};
  • Reject 方法: 直接返回一个已拒绝状态的 Promise。
Promise.reject = (reason) => {
  // 返回一个新的 Promise
  return new Promise((_, reject) => {
    reject(reason);
  });
};
  • Resolve 方法: 直接返回一个已解决状态的 Promise。
Promise.resolve = (value) => {
  // 返回一个新的 Promise
  return new Promise((resolve) => {
    resolve(value);
  });
};

使用手写的 Promise

使用我们手写的 Promise 与使用原生的 Promise 非常相似,只需直接替换原生 Promise 的 API 即可。

// 创建一个 Promise 对象
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, world!');
  }, 1000);
});

// 使用 then 方法处理异步操作的结果
promise.then((value) => {
  console.log(value); // 输出: Hello, world!
}, (reason) => {
  console.log(reason); // 输出: Oops, something went wrong!
});

结论

手写 Promise 的过程不仅是一次技术练习,更是一次对异步编程思想的深刻理解。通过打造自己的 Promise 实现,我们不仅扩展了 JavaScript 工具箱,也拓展了我们对异步编程的认知边界。

常见问题解答

  1. 手写 Promise 与原生 Promise 有什么区别?

    手写 Promise 提供了更多的灵活性,可以根据特定需求进行定制,并添加额外的功能。

  2. 手写 Promise 会比原生 Promise 慢吗?

    这取决于具体的实现,但在大多数情况下,两者之间的性能差异可以忽略不计。

  3. 何时应该使用手写的 Promise?

    当原生 Promise 无法满足需求时,或者当需要对 Promise 行为进行更精细的控制时,可以使用手写的 Promise。

  4. 手写 Promise 会被未来版本的 JavaScript 取代吗?

    不太可能,因为 Promise 已经成为 JavaScript 异步编程的基石,手写 Promise 提供了一种更灵活的方式来使用它们。

  5. 如何确保手写的 Promise 正确无误?

    进行单元测试、编写文档并遵循最佳实践可以帮助确保手写的 Promise 稳定可靠。