我是如何手写一个Promise的

435 阅读4分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战(已经更文6天)

前言

Promise想必大家都不陌生,在工作或者学习中或多或少会使用到。它是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。之前的异步编程,通过回调函数去处理的话,可能会陷入无尽的回调地狱中 - 一个回调嵌套一个回调,子子孙孙无穷尽也。

举个栗子:

// 假设 $ajax 是一个异步事件,success 为回调函数,下一个请求依赖上一个请求的结果
$ajax({
  // ...
  success: function () {
    $ajax({
      // ...
      success: function () {
        $ajax({
          // ...
          success: function () {
            // ...
          },
        });
      },
    });
  },
});

创建一个Promise_

我们先创建一个Promise对象,为了跟真正的Promise区分开来,我们将其命名为Promise_。大家都知道,创建一个Promise对象需要传入一个函数,这个函数有两个入参:resolvereject

我们先来写一个类:

class Promise_ {
  constructor(callback) {
    // 1.判断入参是否为函数
    if (!(callback instanceof Function)) {
      console.error(`Promise resolver ${callback} is not a function`);
      return;
    }
  }
}

我们再来想一下resolvereject的作用是什么。

Promise的实现本质上是发布订阅模式的一种体现。在thencatchfinally等方法传入回调,这些回调函数都是订阅者,会被存在Promise内部的订阅者列表里。在resolve或者reject调用时,循环这些订阅者列表,发布订阅。

那么基于此原理,我们需要在Promise_内部维护两个订阅者列表,分别存储then方法传入的两个回调函数。

所以现在我们的Promise_

class Promise_ {
  status = "pedding"; // 状态:pedding fulfilled rejected
  successDep = []; // 成功时的回调函数集合
  errorDep = []; // 失败时的回调函数集合
  resultVal = undefined; // 执行完毕的值

  constructor(callback) {
    // 1.判断入参是否为函数
    if (!(callback instanceof Function)) {
      console.error(`Promise resolver ${callback} is not a function`);
      return;
    }
  }
}

resolve和reject

我们再来实现一下resolvereject两个方法。这两个方法基本的思路就是在调用时,改变Promise对象内部的状态,分别为fulfilledrejected,并且执行then方法传入的回调函数列表。

class Promise_ {
  status = "pedding"; // 状态:pedding fulfilled rejected
  successDep = []; // 成功时的回调函数集合
  errorDep = []; // 失败时的回调函数集合
  resultVal = undefined; // 执行完毕的值

  constructor(callback) {
    // 1.判断入参是否为函数
    if (!(callback instanceof Function)) {
      console.error(`Promise resolver ${callback} is not a function`);
      return;
    }
    callback(this.resolve, this.reject);
  }

  // notify 发布
  resolve = (value) => {
    // 赋值
    this.status = "fulfilled";
    this.resultVal = value;
    // 发布订阅
    this.successDep.forEach((m) => {
      m(value);
    });
  };
  reject = (value) => {
    // 赋值
    this.status = "rejected";
    this.resultVal = value;
    // 发布订阅
    this.errorDep.forEach((m) => {
      m(value);
    });
  };
}

then

发布订阅的方法实现了,我们再来实现订阅的方法。then方法接受两个参数,这两个参数都要是函数。then方法的第一个参数是fulfilled状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。

  // depend 订阅
  then = (success, error) => {
    // 判断入参是否都为函数
    success = success instanceof Function ? success : () => {};
    error = error instanceof Function ? error : () => {};

    // 判断状态
    if (this.status === "fulfilled") {
      success(this.resultVal);
      return this;
    }
    if (this.status === "rejected") {
      error(this.resultVal);
      return this;
    }

    // 添加订阅者
    this.successDep.push(success);
    this.errorDep.push(error);
    // 链式调用
    return this;
  };

现在一个基本的Promise_类就已经实现了,我们可以来验证一下好使不:

const p = new Promise_((res, rej) => {
  setTimeout(() => {
    rej(233);
  }, 300);
});

p.then(
  (val) => {
    console.log("resolve:", val);
  },
  (err) => {
    console.log("reject:", err);
  }
);

打印:

image.png

catch

catch方法是then方法的别名,用于指定发生错误时的回调函数。如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

实现:

class Promise_ {
  status = "pedding"; // 状态:pedding fulfilled rejected
  successDep = []; // 成功时的回调函数集合
  errorDep = []; // 失败时的回调函数集合
  resultVal = undefined; // 执行完毕的值

  constructor(callback) {
    // 1.判断入参是否为函数
    if (!(callback instanceof Function)) {
      console.error(`Promise resolver ${callback} is not a function`);
      return;
    }
    try {
      callback(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  // notify 发布
  resolve = (value) => {
    // 赋值
    this.status = "fulfilled";
    this.resultVal = value;
    // 发布订阅
    this.successDep.forEach((m) => {
      try {
        m(value);
      } catch (error) {
        this.reject(error);
      }
    });
  };
  reject = (value) => {
    // 赋值
    this.status = "rejected";
    this.resultVal = value;
    // 发布订阅
    this.errorDep.forEach((m) => {
      m(value);
    });
  };

  // depend 订阅
  then = (success, error) => {
    // 判断入参是否都为函数
    success = success instanceof Function ? success : () => {};
    error = error instanceof Function ? error : () => {};

    // 判断状态
    if (this.status === "fulfilled") {
      success(this.resultVal);
      return this;
    }
    if (this.status === "rejected") {
      error(this.resultVal);
      return this;
    }

    // 添加订阅者
    this.successDep.push(success);
    this.errorDep.push(error);
    // 链式调用
    return this;
  };
  catch = (error) => {
    // 判断入参是否都为函数
    error = error instanceof Function ? error : () => {};

    if (this.status === "rejected") {
      error(this.resultVal);
      return this;
    }

    // 添加订阅者
    this.errorDep.push(error);
    // 链式调用
    return this;
  };
}

finally

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

实现:

  finally = (cb) => {
    return this.then(
      () => cb(),
      () => cb()
    );
  };

Promise_.all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,Promise.all方法接受一个数组作为参数。

const p = Promise.all([p1, p2, p3]);
  1. 当传入的数组 Promise 成员的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  2. 当传入的数组 Promise 成员之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

实现:

  // all方法
  static all = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let resArr = [];
      let num = 0;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          resArr[i] = val;
          if (++num === arr.length) {
            res(resArr);
          }
        }).catch((err) => {
          rej([err]);
        });
      }
    });
  };
  // 转换其他类型为Promise_类型
  static resolve = (val) => {
    return new Promise_((res, rej) => {
      res(val);
    });
  };

Promise_.race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

实现:

  // race方法
  static race = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let f = false;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          if (f) return;
          f = true;
          res(val);
        }).catch((err) => {
          if (f) return;
          f = true;
          rej(err);
        });
      }
    });
  };

Promise_.any

ES2021 引入了Promise.any方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

实现:

  // any方法
  static any = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let num = 0;
      let f = false;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          if (f) return;
          f = true;
          res(val);
        }).catch((err) => {
          if (f) return;
          if (++num === arr.length) {
            rej(new Error("any error"));
          }
        });
      }
    });
  };

完整实例

现在,基本完成了一个Promise类,现在贴出完整版的代码:

class Promise_ {
  status = "pedding"; // 状态:pedding fulfilled rejected
  successDep = []; // 成功时的回调函数集合
  errorDep = []; // 失败时的回调函数集合
  resultVal = undefined; // 执行完毕的值

  constructor(callback) {
    // 1.判断入参是否为函数
    if (!(callback instanceof Function)) {
      console.error(`Promise resolver ${callback} is not a function`);
      return;
    }
    try {
      callback(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  // all方法
  static all = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let resArr = [];
      let num = 0;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          resArr[i] = val;
          if (++num === arr.length) {
            res(resArr);
          }
        }).catch((err) => {
          rej([err]);
        });
      }
    });
  };
  // race方法
  static race = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let f = false;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          if (f) return;
          f = true;
          res(val);
        }).catch((err) => {
          if (f) return;
          f = true;
          rej(err);
        });
      }
    });
  };
  // any方法
  static any = (arr) => {
    // 判断入参是否为一个数组
    if (!(arr instanceof Array)) {
      console.error(`Promise resolver ${arr} is not a Array`);
      return;
    }

    // 判断是否为空数组
    if (arr.length === 0) return new Promise_((res) => res([]));

    return new Promise_((res, rej) => {
      let num = 0;
      let f = false;
      for (let i = 0; i < arr.length; i++) {
        let p = null;
        // 检测数组成员是否为Promise_对象
        if (!(arr[i] instanceof Promise_)) {
          p = Promise_.resolve(arr[i]);
        } else {
          p = arr[i];
        }

        p.then((val) => {
          if (f) return;
          f = true;
          res(val);
        }).catch((err) => {
          if (f) return;
          if (++num === arr.length) {
            rej(new Error("any error"));
          }
        });
      }
    });
  };
  // 转换其他类型为Promise_类型
  static resolve = (val) => {
    return new Promise_((res, rej) => {
      res(val);
    });
  };

  // notify 发布
  resolve = (value) => {
    // 赋值
    this.status = "fulfilled";
    this.resultVal = value;
    // 发布订阅
    this.successDep.forEach((m) => {
      try {
        m(value);
      } catch (error) {
        this.reject(error);
      }
    });
  };
  reject = (value) => {
    // 赋值
    this.status = "rejected";
    this.resultVal = value;
    // 发布订阅
    this.errorDep.forEach((m) => {
      m(value);
    });
  };

  // depend 订阅
  then = (success, error) => {
    // 判断入参是否都为函数
    success = success instanceof Function ? success : () => {};
    error = error instanceof Function ? error : () => {};

    // 判断状态
    if (this.status === "fulfilled") {
      success(this.resultVal);
      return this;
    }
    if (this.status === "rejected") {
      error(this.resultVal);
      return this;
    }

    // 添加订阅者
    this.successDep.push(success);
    this.errorDep.push(error);
    // 链式调用
    return this;
  };
  catch = (error) => {
    // 判断入参是否都为函数
    error = error instanceof Function ? error : () => {};

    if (this.status === "rejected") {
      error(this.resultVal);
      return this;
    }

    // 添加订阅者
    this.errorDep.push(error);
    // 链式调用
    return this;
  };
  finally = (cb) => {
    return this.then(
      () => cb(),
      () => cb()
    );
  };
}

结语

Promise对于前端开发来说是非常重要的,在平时工作中或者面试的时候一定会用到。如何深入理解Promise呢?那一定是自己手写实现一个Promise。本文实现了一个简易版的Promise,基本上该有的都有了。

如果看完这篇文章,对您有所帮助的话,还烦请您点个赞,点点关注,祝您生活愉快。