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 / 前端工程化 / 实战技巧 等领域。

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

相关推荐
数据知道6 分钟前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
dr李四维6 分钟前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久12 分钟前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain29 分钟前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏36 分钟前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
GISer_Jing1 小时前
React-Markdown详解
前端·react.js·前端框架
太阳花ˉ1 小时前
React(九)React Hooks
前端·react.js
朴拙数科2 小时前
技术长期主义:用本分思维重构JavaScript逆向知识体系(一)Babel、AST、ES6+、ES5、浏览器环境、Node.js环境的关系和处理流程
javascript·重构·es6
拉不动的猪2 小时前
vue与react的简单问答
前端·javascript·面试