Skip to content

手写promise

手写promise ​

https://www.bilibili.com/video/BV1ug411w7sb

js
Promise.all;

Promise.all 是静态方法,不能写在 prototype 上,如果是 then,catch 等实例方法,要写在 promise.prototype 上

用类写的话,直接 static

js
if (promise instanceof Promise) {
}

这个判断可以省略掉,直接写成

js
Promise.resolve(promise);

这样不管是常量还是 promise 都包装成了 Promise

<!-- more -->

Promise/A+规范 ​

官方的地址为:https://promisesaplus.com/

林三心版本 Promise ​

https://juejin.cn/post/6994594642280857630

js
class MyPromise {
  constructor(executor) {
    this.initValue();
    this.initBind();
    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }
  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 =
      typeof onFulfilled === "function" ? onFulfilled : (val) => val;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw new Error(reason);
          };
    var thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (cb) => {
        queueMicrotask(() => {
          try {
            const x = cb(this.PromiseResult);
            if (x === thenPromise) {
              throw new Error("不能返回自身");
            }
            if (x instanceof MyPromise) {
              x.then(resolve, reject);
            } else {
              resolve(x);
            }
          } 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;
  }

  // 语法糖
  finally(cb) {
    return then(
      (res) => MyPromise.resolve(cb()).then(() => res),
      (err) => MyPromise.resolve(cb()).then(() => throw err)
    );
  }

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

  static all(promises) {
    return new Promise((resolve, reject) => {
        let result = []
        let count = 0 // count 是确保 myPromiseAll 在所有 Promise 都完成后再返回结果的关键。它通过跟踪已完成的 Promise 数量来确保不会提前返回结果。
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(res => {
                result[i] = res
                count++
                if (count === promises.length) {
                    resolve(result)
                }
            }).catch(err => {
                reject(err)
            })
        }
    })
  }

  static race(promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            // 简写 promises[i].then(resolve, reject)
            promises[i].then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        }
    })
  }

  static allSettled(promises) {
    return new Promise((resolve, reject) => {
        let result = [];
        let count = 0;
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(res => {
                result[i] = {
                    status: 'fulfilled',
                    value: res
                }
            }).catch(err => {
                result[i] = {
                    status: 'rejected',
                    reason: err
                }
            }).finally(() => {
                count++
                if (count === promises.length) {
                    resolve(result)
                }
            })
        }
    })
  }

  static any(promises) {
    return new Promise((resolve, reject) => {
        let count = 0;
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve).catch(() => {
                count++;
                if (count === promises.length) {
                    reject(new AggregateError("All promises were rejected"));
                }
            });
        }
    })
  }
}

const test2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject(222);
  }, 2000);
});

const test = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject(111);
  }, 1000);
});

const test3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject(333);
  }, 3000);
});
// Promise.race([test2, test, test3]).then(console.log);
// MyPromise.race([test2, test, test3]).then(console.log);

// Promise.all([test2, test, test3]).then(console.log, console.error);
// MyPromise.all([test2, test, test3]).then(console.log, console.error);

// Promise.allSettled([test2, test, test3]).then(console.log, console.error);
// MyPromise.allSettled([test2, test, test3]).then(console.log, console.error);

Promise.any([test2, test, test3]).then(console.log, console.error);
MyPromise.any([test2, test, test3]).then(console.log, console.error);

<!-- more -->

术语 ​

“promise” is an object or function with a then method whose behavior conforms to this specification. “thenable” is an object or function that defines a then method. “value” is any legal JavaScript value (including undefined, a thenable, or a promise). “exception” is a value that is thrown using the throw statement. “reason” is a value that indicates why a promise was rejected.

  1. “promise”:是一个具有 then 方法的对象或者函数,它的行为符合该规范。

  2. “thenable”:是一个定义了 then 方法的对象或者函数。

  3. “value”:可以是任何一个合法的 JavaScript 的值(包括 undefined、thenable 或 promise)。

  4. “exception”:是一个异常,是在 Promise 里面可以用 throw 语句抛出来的值。

  5. “reason”:是一个 Promise 里 reject 之后返回的拒绝原因。

状态描述 ​

A promise must be in one of three states: pending, fulfilled, or rejected. When pending, a promise: may transition to either the fulfilled or rejected state. When fulfilled, a promise: must not transition to any other state. must have a value, which must not change. When rejected, a promise: must not transition to any other state. must have a reason, which must not change. Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.

  1. 一个 Promise 有三种状态:pending、fulfilled 和 rejected。

  2. 当状态为 pending 状态时,即可以转换为 fulfilled 或者 rejected 其中之一

  3. 当状态为 fulfilled 状态时,就不能转换为其他状态了,必须返回一个不能再改变的值

  4. 当状态为 rejected 状态时,同样也不能转换为其他状态,必须有一个原因的值也不能改变

then 方法 ​

一个 Promise 必须拥有一个 then 方法来访问它的值或者拒绝原因

then 方法有两个参数

promise.then(onFulfilled, onRejected)

onFulfilled 和 onRejected 特性

如果 onFulfilled 是函数,则当 Promise 执行结束之后必须被调用,最终返回值为 value,其调用次数不可超过一次。而 onRejected 除了最后返回的是 reason 外,其他方面和 onFulfilled 在规范上的表述基本一样

多次调用 ​

then 方法其实可以被一个 Promise 调用多次,且必须返回一个 Promise 对象。then 的写法如下所示,其中 Promise1 执行了 then 的方法之后,返回的依旧是个 Promise2,然后我们拿着 Promise2 又可以执行 then 方法,而 Promise2 是一个新的 Promise 对象,又可以继续进行 then 方法调用

promise2 = promise1.then(onFulfilled, onRejected);

一步步实现 Promise ​

按照 Promise/A+ 的规范,第一步就是构造函数

构造函数 ​

Promise 构造函数接受一个 executor 函数,executor 函数执行完同步或者异步操作后,调用它的两个参数 resolve 和 reject

js
function Promise(executor) {
  var self = this;
  self.status = "pending"; // Promise当前的状态
  self.data = undefined; // Promise的值
  self.onResolvedCallback = []; // Promise resolve时的回调函数集
  self.onRejectedCallback = []; // Promise reject时的回调函数集
  executor(resolve, reject); // 执行executor并传入相应的参数
}

从上面的代码中可以看出,我们先定义了一个 Promise 的初始状态 pending,以及参数执行函数 executor,并且按照规范设计了一个 resolve 回调函数集合数组 onResolvedCallback 以及 一个 reject 回调函数集合数组,那么构造函数的初始化就基本完成了

接下来我们看看还需要添加什么东西呢?那就是需要在构造函数中完善 resolve 和 reject 两个函数,完善之后的代码如下

js
function Promise(executor) {
  var self = this;
  self.status = "pending"; // Promise当前的状态
  self.data = undefined; // Promise的值
  self.onResolvedCallback = []; // Promise resolve时的回调函数集
  self.onRejectedCallback = []; // Promise reject时的回调函数集
  function resolve(value) {
    // TODO
  }
  function reject(reason) {
    // TODO
  }
  try {
    // 考虑到执行过程中有可能出错,所以我们用try/catch块给包起
    executor(resolve, reject); // 执行executor
  } catch (e) {
    reject(e);
  }
}

resolve 和 reject 内部应该怎么实现呢?我们根据规范知道这两个方法主要做的事情就是返回对应状态的值 value 或者 reason,并把 Promise 内部的 status 从 pending 变成对应的状态,并且这个状态在改变了之后是不可以逆转的

js
function Promise(executor) {
  // ...上面的省略
  function resolve(value) {
    if (self.status === "pending") {
      self.status = "resolved";
      self.data = value;
      for (var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value);
      }
    }
  }

  function reject(reason) {
    if (self.status === "pending") {
      self.status = "rejected";
      self.data = reason;
      for (var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason);
      }
    }
  }
  // 下面的省略
}

上述代码所展示的,基本就是在判断状态为 pending 之后,把状态改为相应的值,并把对应的 value 和 reason 存在内部的 data 属性上面,之后执行相应的回调函数

实现 then 方法 ​

then 方法是 Promise 执行完之后可以拿到 value 或者 reason 的方法,并且还要保持 then 执行之后,返回的依旧是一个 Promise 方法,还要支持多次调用

js
// then方法接收两个参数onResolved和onRejected,分别为Promise成功或失败后的回调
Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2;
  // 根据标准,如果then的参数不是function,则需要忽略它
  onResolved = typeof onResolved === "function" ? onResolved : function (v) {};
  onRejected = typeof onRejected === "function" ? onRejected : function (r) {};
  if (self.status === "resolved") {
    return (promise2 = new Promise(function (resolve, reject) {}));
  }
  if (self.status === "rejected") {
    return (promise2 = new Promise(function (resolve, reject) {}));
  }
  if (self.status === "pending") {
    return (promise2 = new Promise(function (resolve, reject) {}));
  }
};

从上面的代码中可以看到,我们在 then 方法内部先初始化了 Promise2 的对象,用来存放执行之后返回的 Promise,并且还需要判断 then 方法传参进来的两个参数必须为函数,这样才可以继续执行。

上面我只是搭建了 then 方法框架的整体思路,但是不同状态的返回细节处理也需要完善,通过仔细阅读标准,完善之后的 then 的代码如下

js
Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2;
  // 根据标准,如果then的参数不是function,则需要忽略它
  onResolved =
    typeof onResolved === "function" ? onResolved : function (value) {};
  onRejected =
    typeof onRejected === "function" ? onRejected : function (reason) {};
  if (self.status === "resolved") {
    // 如果promise1的状态已经确定并且是resolved,我们调用onResolved,考虑到有可能throw,所以还需要将其包在try/catch块里
    return (promise2 = new Promise(function (resolve, reject) {
      try {
        var x = onResolved(self.data);
        if (x instanceof Promise) {
          // 如果onResolved的返回值是一个Promise对象,直接取它的结果作为promise2的结果
          x.then(resolve, reject);
        }
        resolve(x); // 否则,以它的返回值作为promise2的结果
      } catch (e) {
        reject(e); // 如果出错,以捕获到的错误作为promise2的结果
      }
    }));
  }
  // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数
  if (self.status === "rejected") {
    return (promise2 = new Promise(function (resolve, reject) {
      try {
        var x = onRejected(self.data);
        if (x instanceof Promise) {
          x.then(resolve, reject);
        }
      } catch (e) {
        reject(e);
      }
    }));
  }
  if (self.status === "pending") {
    // 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,只能等到Promise的状态确定后,才能确定如何处理
    return (promise2 = new Promise(function (resolve, reject) {
      self.onResolvedCallback.push(function (value) {
        try {
          var x = onResolved(self.data);
          if (x instanceof Promise) {
            x.then(resolve, reject);
          }
        } catch (e) {
          reject(e);
        }
      });
      self.onRejectedCallback.push(function (reason) {
        try {
          var x = onRejected(self.data);
          if (x instanceof Promise) {
            x.then(resolve, reject);
          }
        } catch (e) {
          reject(e);
        }
      });
    }));
  }
};

根据上面的代码可以看出,我们基本实现了一个符合标准的 then 方法。但是标准里提到了,还要支持不同的 Promise 进行交互,关于不同的 Promise 交互其实 Promise 标准说明(https://promisesaplus.com/#point-46)中有提到。其中详细指定了如何通过 then 的实参返回的值来决定 Promise2 的状态

关于为何需要不同的 Promise 实现交互,原因应该是 Promise 并不是 JS 一开始存在的标准,如果你使用的某一个库中封装了一个 Promise 的实现,想象一下如果它不能跟你自己使用的 Promise 实现交互的情况,其实还是会有问题的,因此我们还需要调整一下 then 方法中执行 Promise 的方法

另外还有一个需要注意的是,在 Promise/A+ 规范中,onResolved 和 onRejected 这两项函数需要异步调用,关于这一点,标准里面是这么说的

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

所以我们需要对代码做一点变动,即在处理 Promise 进行 resolve 或者 reject 的时候,加上 setTimeout(fn, 0)

完整版的代码 ​

js
try {
  module.exports = Promise;
} catch (e) {}
function Promise(executor) {
  var self = this;
  self.status = "pending";
  self.onResolvedCallback = [];
  self.onRejectedCallback = [];
  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }
    setTimeout(function () {
      // 异步执行所有的回调函数
      if (self.status === "pending") {
        self.status = "resolved";
        self.data = value;
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value);
        }
      }
    });
  }
  function reject(reason) {
    setTimeout(function () {
      // 异步执行所有的回调函数
      if (self.status === "pending") {
        self.status = "rejected";
        self.data = reason;
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason);
        }
      }
    });
  }
  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}
function resolvePromise(promise2, x, resolve, reject) {
  var then;
  var thenCalledOrThrow = false;
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise!"));
  }
  if (x instanceof Promise) {
    if (x.status === "pending") {
      x.then(function (v) {
        resolvePromise(promise2, v, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    try {
      then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          function rs(y) {
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            return resolvePromise(promise2, y, resolve, reject);
          },
          function rj(r) {
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            return reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (thenCalledOrThrow) return;
      thenCalledOrThrow = true;
      return reject(e);
    }
  } else {
    resolve(x);
  }
}
Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2;
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function (v) {
          return v;
        };
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function (r) {
          throw r;
        };
  if (self.status === "resolved") {
    return (promise2 = new Promise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onResolved
        try {
          var x = onResolved(self.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
  if (self.status === "rejected") {
    return (promise2 = new Promise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onRejected
        try {
          var x = onRejected(self.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
  if (self.status === "pending") {
    // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return (promise2 = new Promise(function (resolve, reject) {
      self.onResolvedCallback.push(function (value) {
        try {
          var x = onResolved(value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
      self.onRejectedCallback.push(function (reason) {
        try {
          var x = onRejected(reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};
Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};
// 最后这个是测试用的,后面会说
Promise.deferred = Promise.defer = function () {
  var dfd = {};
  dfd.promise = new Promise(function (resolve, reject) {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

最终版的 Promise 的实现还是需要经过规范的测试(Promise /A+ 规范测试的工具地址为:https://github.com/promises-aplus/promises-tests),需要暴露一个 deferred 方法(即 exports.deferred 方法),上面提供的代码中我已经将其加了进去

js
npm i -g promises-aplus-tests
promises-aplus-tests Promise.js

模拟异步任务 ​

queueMicrotask

https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide

setTimeout

因为这个是宏任务,所以不适合模拟

参考链接 ​

基于 VitePress 构建 · 专注前端与 AI 实战