返回

手写Promise实战,面试官直呼牛x

前端

手把手教你编写自己的 Promise

在前端开发中,Promise 是一个必不可少的工具,它允许我们处理异步操作并编写更简洁和可维护的代码。虽然 Promise 内部的机制可能看起来很复杂,但理解其工作原理至关重要。在这篇文章中,我们将深入探讨如何从头开始编写一个 Promise,并为你提供一个易于理解的逐步指南。

1. Promise 的核心:一个简单又强大的类

我们从创建一个 Promise 类开始。它是一个蓝图,用于生成 Promise 实例,其中包含处理异步操作所需的基本功能。

class Promise {
  constructor(executor) {
    this.state = "pending"; // Promise 的初始状态
    this.value = undefined; // 存储最终结果(如果 Promise 成功)
    this.reason = undefined; // 存储失败原因(如果 Promise 失败)
    this.onFulfilledCallbacks = []; // 成功回调函数队列
    this.onRejectedCallbacks = []; // 失败回调函数队列

    // 立即执行 executor 函数,传递 resolve 和 reject 函数
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (err) {
      this.reject(err); // 如果 executor 函数抛出异常,则直接拒绝 Promise
    }
  }
}

2. 解析和拒绝:改变 Promise 的状态

接下来,我们定义 resolve 和 reject 函数,它们用于改变 Promise 的状态并存储结果或失败原因。

resolve(value) {
  if (this.state !== "pending") return; // 确保只有在 Promise 处于待定状态时才能解析它

  this.state = "fulfilled"; // 将状态更改为已完成
  this.value = value; // 存储最终结果

  // 执行所有已注册的成功回调函数
  for (const callback of this.onFulfilledCallbacks) {
    callback(value);
  }
}

reject(reason) {
  if (this.state !== "pending") return; // 确保只有在 Promise 处于待定状态时才能拒绝它

  this.state = "rejected"; // 将状态更改为已拒绝
  this.reason = reason; // 存储失败原因

  // 执行所有已注册的失败回调函数
  for (const callback of this.onRejectedCallbacks) {
    callback(reason);
  }
}

3. then:连接 Promise,处理结果和错误

then 方法是 Promise 的核心。它允许我们链接多个 Promise 并处理结果和错误。

then(onFulfilled, onRejected) {
  return new Promise((resolve, reject) => {
    // 如果 onFulfilled 是函数,将其添加到成功回调队列
    if (typeof onFulfilled === "function") {
      this.onFulfilledCallbacks.push(() => {
        try {
          const result = onFulfilled(this.value);
          resolve(result); // 将成功回调函数的结果解析为新的 Promise
        } catch (err) {
          reject(err); // 如果成功回调函数抛出异常,则拒绝新的 Promise
        }
      });
    }

    // 如果 onRejected 是函数,将其添加到失败回调队列
    if (typeof onRejected === "function") {
      this.onRejectedCallbacks.push(() => {
        try {
          const result = onRejected(this.reason);
          resolve(result); // 将失败回调函数的结果解析为新的 Promise
        } catch (err) {
          reject(err); // 如果失败回调函数抛出异常,则拒绝新的 Promise
        }
      });
    }

    // 如果 Promise 已解决或拒绝,立即执行回调函数
    if (this.state !== "pending") {
      this.state === "fulfilled"
        ? this.onFulfilledCallbacks[0]()
        : this.onRejectedCallbacks[0]();
    }
  });
}

4. catch:简化错误处理

catch 方法是一个语法糖,它允许我们只指定一个失败回调函数,以简化错误处理。

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

5. finally:无论 Promise 结果如何都执行的代码

finally 方法允许我们在 Promise 无论成功还是失败时都执行一些代码。

finally(onFinally) {
  return this.then(
    () => onFinally(), // 成功时执行 onFinally
    () => onFinally() // 失败时执行 onFinally
  );
}

示例:将 Promise 投入实践

让我们通过一个示例来看一个 Promise 是如何工作的:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("数据已加载!");
  }, 2000);
});

myPromise
  .then(result => {
    console.log(result); // 输出:数据已加载!
  })
  .catch(error => {
    console.error(error); // 如果发生错误,将在此处处理
  });

结论

通过编写自己的 Promise,你可以深入了解其内部机制,并获得对其功能的更深入理解。通过掌握 Promise,你可以编写异步代码,提高可维护性和代码可读性,从而提升你的前端开发技能。

常见问题解答

  • 为什么使用 Promise?
    Promise 使得管理异步操作变得更容易,避免了回调函数的“地狱”。
  • Promise 的状态有哪些?
    有三个状态:“pending”、“fulfilled”和“rejected”。
  • then 方法如何工作?
    then 方法返回一个新的 Promise,该 Promise 在原始 Promise 解析或拒绝时被解析。
  • catch 方法与 then 方法有何不同?
    catch 方法是一个简写,它允许只指定一个失败回调函数。
  • finally 方法有什么用?
    finally 方法允许在 Promise 无论成功还是失败时都执行一些代码。