神族九帝's blog 神族九帝's blog
首页
网盘 (opens new window)
线报 (opens new window)
商城 (opens new window)
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 基础篇
  • 进阶篇
  • 高级篇
  • 计算机基础
  • 高频考点
  • 精简题
  • 综合问题
  • 复习题
  • vue
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • 更多
  • 网站
  • 资源
  • Vue资源
  • 收藏的一些API
  • 未来要做的事
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 导航
GitHub (opens new window)

神族九帝,永不言弃

首页
网盘 (opens new window)
线报 (opens new window)
商城 (opens new window)
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 基础篇
  • 进阶篇
  • 高级篇
  • 计算机基础
  • 高频考点
  • 精简题
  • 综合问题
  • 复习题
  • vue
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • 更多
  • 网站
  • 资源
  • Vue资源
  • 收藏的一些API
  • 未来要做的事
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 导航
GitHub (opens new window)
  • 复习指导

  • HTML

  • CSS

  • JavaScript

  • 设计模式

  • 浏览器

  • 手写系列

    • 继承
    • call,apply,bind
    • 手写promise
      • Promise/A+规范
      • 林三心版本 Promise
        • 术语
        • 状态描述
        • then 方法
        • 多次调用
      • 一步步实现 Promise
        • 构造函数
        • 实现 then 方法
      • 完整版的代码
        • 模拟异步任务
      • 参考链接
    • 手写基础js
    • 手写排序
    • 并行限制的Promise
  • Vue

  • Webpack

  • Http

  • 前端优化

  • 项目

  • 面试真题

  • 算法

  • 精选文章

  • 八股文

  • 前端工程化

  • 面试题
  • 手写系列
wu529778790
2021-09-20

手写promise

https://www.bilibili.com/video/BV1ug411w7sb (opens new window)

Promise.all;

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

用类写的话,直接 static

if (promise instanceof Promise) {
}

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

Promise.resolve(promise);

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

# Promise/A+规范

官方的地址为:https://promisesaplus.com/ (opens new window)

# 林三心版本 Promise

https://juejin.cn/post/6994594642280857630 (opens new window)

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 MyPromise((resolve, reject) => {
      let result = [];
      // 这里使用count是为了后面用length判断会失误,比如直接result[index]这里index如果是length,前面的结果并没有输入,而是undefined,就会错误
      let count = 0;
      let addData = (value, index) => {
        count++;
        // 这里要用index,不能用push,push会导致顺序混乱
        result[index] = value;
        // 用count计数,不能用数组长度判断
        if (count === promises.length) {
          resolve(result);
        }
      };
      promises.forEach((promise) => {
        // 这里的判断可以改成Promise.resolve()无论里面是不是常量都会被包成Promise
        if (promise instanceof MyPromise) {
          promise.then(
            (res) => {
              addData(res);
            },
            (err) => {
              reject(err);
            }
          );
        } else {
          addData(res);
        }
      });
    });
  }

  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        Promise.resolve(promise).then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
        // if (promise instanceof Promise) {
        //   promise.then(
        //     (res) => {
        //       resolve(res);
        //     },
        //     (err) => {
        //       rejecte(err);
        //     }
        //   );
        // } else {
        //   resolve(promise);
        // }
      });
    });
  }

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

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      let count = 0;
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            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);

# 术语

“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

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 两个函数,完善之后的代码如下

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 变成对应的状态,并且这个状态在改变了之后是不可以逆转的

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 方法,还要支持多次调用

// 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 的代码如下

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 (opens new window))中有提到。其中详细指定了如何通过 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)

# 完整版的代码

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 (opens new window)),需要暴露一个 deferred 方法(即 exports.deferred 方法),上面提供的代码中我已经将其加了进去

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 (opens new window)

setTimeout

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

# 参考链接

  • https://juejin.cn/post/6994594642280857630 (opens new window)
  • https://segmentfault.com/a/1190000021281507 (opens new window)
编辑 (opens new window)
上次更新: 2022/01/16, 21:36:08
call,apply,bind
手写基础js

← call,apply,bind 手写基础js→

最近更新
01
好玩的docker
07-04
02
我的第一个NFT
07-03
03
海外迅雷
06-01
更多文章>
Power by vuepress | Copyright © 2015-2023 神族九帝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×