JavaScript 高级之手写Promise

Promise 是 JavaScript 异步编程的核心概念之一,我们将从基础到高级,一步步实现一个 MyPromise 类,以深入理解其内部机制。

1. 初步实现 resolvereject

首先,我们来看一个 Promise 的基础用法:

javascript 复制代码
const p1 = new Promise((resolve, reject) => {
  resolve("success");
  reject("fail");
});

const p2 = new Promise((resolve, reject) => {
  reject("fail");
  resolve("success");
});

console.log(p1);
console.log(p2);

以上例子执行的结果是

在这个例子中,我们可以观察到:

  • resolvereject 被调用后,Promise 状态(PromiseState)就会锁定,后续的 resolvereject 调用不会生效。
  • resolve("success") 之后,reject("fail") 无效;同理,reject("fail") 之后,resolve("success") 也不会影响最终的状态。

基于这个行为,我们初步实现 MyPromise

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.initValue();
    try {
      // 执行传进来的函数
      executor(this.resolve, this.reject);
    } catch (e) {
      // 捕捉到错误直接执行reject
      this.reject(e);
    }
  }

  initValue() {
    this.PromiseState = "pending";
    this.PromiseResult = null;
  }

  resolve(value) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "fulfilled";
    this.PromiseResult = value;
  }

  reject(reason) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "rejected";
    this.PromiseResult = reason;
  }
}

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
  reject("fail");
});

console.log(mp1);

然而,执行后会报错:

原因分析

executor(this.resolve, this.reject); 这行代码中,resolvereject 直接被调用,此时 this 指向 windowundefined(在严格模式下)。

解决方案

  • 使用 bind 绑定 this,确保 resolvereject 正确指向 MyPromise 实例。

优化后:

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.initValue();
    this.initBind();
    try {
      // 执行传进来的函数
      executor(this.resolve, this.reject);
    } catch (e) {
      // 捕捉到错误直接执行reject
      this.reject(e);
    }
  }

  initValue() {
    this.PromiseState = "pending";
    this.PromiseResult = null;
  }

  initBind() {
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
  }

  resolve(value) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "fulfilled";
    this.PromiseResult = value;
  }

  reject(reason) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "rejected";
    this.PromiseResult = reason;
  }
}

2. 实现 then 方法

then 方法的作用是:

  • 当 Promise 变为 fulfilled 时,执行 onFulfilled 回调,并传入 resolve 的值。
  • 当 Promise 变为 rejected 时,执行 onRejected 回调,并传入 reject 的值。

示例代码:

javascript 复制代码
const p3 = new Promise((resolve, reject) => {
  resolve(1);
});

const p4 = new Promise((resolve, reject) => {
  reject("失败");
});

p3.then(
  (res) => console.log(res),
  (error) => {
    console.log(error);
  }
);

p4.then(
  (res) => console.log(res),
  (error) => {
    console.log(error);
  }
);

这段代码的执行结果是

实现 then 方法:

javascript 复制代码
then(onFulfilled, onRejected) {
  onFulfilled = onFulfilled || ((value) => value);
  onRejected =
    onRejected ||
    ((reason) => {
      throw reason;
    });

  if (this.PromiseState === "fulfilled") {
    onFulfilled(this.PromiseResult);
  } else if (this.PromiseState === "rejected") {
    onRejected(this.PromiseResult);
  }
}

3. 处理异步任务、值透传和链式调用

3.1 处理异步任务

如果 Promise 内部有异步任务,如 setTimeout,当 then 执行时,PromiseState 仍然是 pending。我们需要存储 then 的回调,待 resolvereject 执行后再调用。

示例代码:

javascript 复制代码
const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

p5.then((res) => res * 2).then((res) => console.log(res));

优化 MyPromise

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.initValue();
    this.initBind();
    try {
      // 执行传进来的函数
      executor(this.resolve, this.reject);
    } catch (e) {
      // 捕捉到错误直接执行reject
      this.reject(e);
    }
  }

  initValue() {
    this.PromiseState = "pending";
    this.PromiseResult = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
  }

  initBind() {
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
  }

  resolve(value) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "fulfilled";
    this.PromiseResult = value;
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.PromiseResult);
    }
  }

  reject(reason) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "rejected";
    this.PromiseResult = reason;
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.PromiseResult);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || ((value) => value);
    onRejected =
      onRejected ||
      ((reason) => {
        throw reason;
      });
    if (this.PromiseState === "fulfilled") {
      onFulfilled(this.PromiseResult);
    } else if (this.PromiseState === "rejected") {
      onRejected(this.PromiseResult);
    } else if (this.PromiseState === "pending") {
      this.onFulfilledCallbacks.push(onFulfilled.bind(this));
      this.onRejectedCallbacks.push(onRejected.bind(this));
    }
  }
}

3.2 处理值透传和链式调用

在 Promise 规范中,then 需要返回一个新的 Promise,这样才能支持链式调用。

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.initValue();
    this.initBind();
    try {
      // 执行传进来的函数
      executor(this.resolve, this.reject);
    } catch (e) {
      // 捕捉到错误直接执行reject
      this.reject(e);
    }
  }

  initValue() {
    this.PromiseState = "pending";
    this.PromiseResult = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
  }

  initBind() {
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
  }

  resolve(value) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "fulfilled";
    this.PromiseResult = value;
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.PromiseResult);
    }
  }

  reject(reason) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "rejected";
    this.PromiseResult = reason;
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.PromiseResult);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || ((value) => value);
    onRejected =
      onRejected ||
      ((reason) => {
        throw reason;
      });
    const thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (callback) => {
        try {
          const result = callback(this.PromiseResult);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (error) {
          reject(error);
        }
      };
      if (this.PromiseState === "fulfilled") {
        resolvePromise(onFulfilled);
      } else if (this.PromiseState === "rejected") {
        resolvePromise(onRejected);
      } else if (this.PromiseState === "pending") {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
      }
    });
    return thenPromise;
  }
}

4. 实现 all, race, any, allSettled

这些方法是 Promise 的静态方法,涉及多个 Promise 的组合处理。

Promise.all

等待所有 Promise 完成,返回结果数组,如果有一个失败,则返回失败的结果。

Promise.race

返回第一个完成的 Promise 结果。

Promise.any

等待至少一个 Promise 成功,全部失败则返回 AggregateError

Promise.allSettled

等待所有 Promise 完成,返回包含 { status, value } 的结果数组。

完整代码如下:

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.initValue();
    this.initBind();
    try {
      // 执行传进来的函数
      executor(this.resolve, this.reject);
    } catch (e) {
      // 捕捉到错误直接执行reject
      this.reject(e);
    }
  }

  initValue() {
    this.promiseStatus = "pending";
    this.promiseValue = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
  }
  initBind() {
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
  }
  resolve(value) {
    if (this.promiseStatus !== "pending") return;
    this.promiseStatus = "fulfilled";
    this.promiseValue = value;
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.promiseValue);
    }
  }
  reject(reason) {
    if (this.promiseStatus !== "pending") return;
    this.promiseStatus = "rejected";
    this.promiseValue = reason;
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.promiseResult);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || ((value) => value);
    onRejected =
      onRejected ||
      ((reason) => {
        throw reason;
      });
    const thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (callback) => {
        try {
          const result = callback(this.promiseResult);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (error) {
          reject(error);
        }
      };
      if (this.promiseStatus === "fulfilled") {
        resolvePromise(onFulfilled);
      } else if (this.promiseStatus === "rejected") {
        resolvePromise(onRejected);
      } else if (this.promiseStatus === "pending") {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
      }
    });
    return thenPromise;
  }

  static all(promises) {
    let count = 0;
    const result = [];
    return new MyPromise((resolve, reject) => {
      const addData = (res, index) => {
        result[index] = res;
        count++;
        if (count === promises.length) resolve(result);
      };

      promises.forEach((promise, index) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              addData(res, index);
            },
            (error) => {
              reject(error);
            }
          );
        } else {
          addData(promise, index);
        }
      });
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              resolve(res);
            },
            (error) => {
              reject(error);
            }
          );
        } else {
          resolve(promise);
        }
      });
    });
  }

  static any(promises) {
    let count = 0;
    return new MyPromise((resolve, reject) => {
      const addData = () => {
        count++;
        if (count === promises.length)
          reject(new AggregateError("All promises were rejected"));
      };
      promises.forEach((promise) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              resolve(res);
            },
            (error) => {
              addData();
            }
          );
        } else {
          resolve(promise);
        }
      });
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      let count = 0;
      const results = [];
      const addData = (status, value, index) => {
        results[index] = { status, value };
        count++;
        if (count === promises.length) {
          resolve(results);
        }
      };
      promises.forEach((promise, index) => {
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              addData("fulfilled", res, index);
            },
            (err) => {
              addData("rejected", err, index);
            }
          );
        } else {
          addData("fulfilled", promise, index);
        }
      });
    });
  }
}

5. 总结

我们从最基础的 resolve/reject 开始,逐步实现 then、异步处理、值透传、链式调用,并最终完成 all/race/any/allSettled 方法,完整实现了一个 Promise 类,深入理解了其运行机制。

📢 关于作者

嗨!我是头发秃头小宝贝,一名热爱技术分享的开发者,专注于Vue / 前端工程化 / 实战技巧 等领域。

如果这篇文章对你有所帮助,别忘了 点赞 👍收藏 ⭐关注 👏,你的支持是我持续创作的最大动力**!**

相关推荐
颜酱1 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
失忆爆表症2 小时前
05_UI 组件库集成指南:Shadcn/ui + Tailwind CSS v4
前端·css·ui
小迷糊的学习记录2 小时前
Vuex 与 pinia
前端·javascript·vue.js
发现一只大呆瓜2 小时前
前端性能优化:图片懒加载的三种手写方案
前端·javascript·面试
不爱吃糖的程序媛2 小时前
Flutter 与 OpenHarmony 通信:Flutter Channel 使用指南
前端·javascript·flutter
利刃大大2 小时前
【Vue】Element-Plus快速入门 && Form && Card && Table && Tree && Dialog && Menu
前端·javascript·vue.js·element-plus
NEXT063 小时前
AI 应用工程化实战:使用 LangChain.js 编排 DeepSeek 复杂工作流
前端·javascript·langchain
念风零壹3 小时前
AI 时代的前端技术:从系统编程到 JavaScript/TypeScript
前端·ai
光影少年3 小时前
react的hooks防抖和节流是怎样做的
前端·javascript·react.js
小毛驴8504 小时前
Vue 路由示例
前端·javascript·vue.js