title: 如何实现一个Promise date: 2019-11-22 21:27:00 updated: 2019-11-22 21:27:00 photos:

  • https://img.yzmblog.top/blog/network.jpg tags:
  • 前端
  • Javascript
  • ES6 categories:
  • Javascript excerpt: 出现解决了js中的回调地狱的问题,使代码更简洁,是ES6中的规范和重要特性。它的使用很简单,但你知道它是怎么样实现的吗~~ 现在我们就来看一下Promise究竟是怎么样实现的

引言

Promise出现解决了 js 中的回调地狱的问题,使代码更简洁,是 ES6 中的规范和重要特性。它的使用很简单,但你知道它是怎么样实现的吗~~
现在我们就来看一下 Promise 究竟是怎么样实现的 😄

  • promise 规范
    Promise 规范是 Promise 函数中需要遵循的规则, ES6 中使用的 Promise,它就是遵循Promise/A+规范的。
    既然是有规则可循的,那我们根据规则来一步步实现Promise

1.创建 Promise 类

看一下Promise是如何使用的

const promise = new Promise((resolve, reject) => {
  try {
    resolve("123");
  } catch (err) {
    reject("error");
  }
});

promise.then((msg) => {
  console.log(msg);
});
  • 首先它是一个构造方法,并且接收一个function作为参数,
    而这个function中有resolvereject两个方法。resolve代表成功后返回的值,reject代表拒绝返回的原因

根据Promise的使用来创建一个叫MyPromise的类

/**
 *  @params {function} callback 需要执行的业务方法
 */
class MyPromise {
  // 构造方法接收一个function
  constructor(callback) {
    callback(this.resolve, this.reject); // 调用此function
  }
  resolve = (value) => {}; // callback中执行的resolve方法
  reject = (reason) => {}; // callback中执行的reject方法
}

// 测试
var test = new MyPromise((resolve, reject) => {
  console.log("my promise is running!");
}); // 打印出 my promise is running!

2.三种状态

现在我们创建的类已经可以执行传入的方法了,但是它传入的resolvereject方法是有什么用的呢?
我们接着看Promise 规范

  • 根据规范可知Promise有三种状态 pending(等待),fulfilled(完成),rejected(拒绝)。
  • 当状态为pending时,Promise 可以变为fulfilledrejected状态
  • 当状态为fulfilled时,Promise 不能改变其状态;必须有值且不能改变
  • 当状态为rejected时,Promise 不能改变其状态;必须有拒绝的原因且不能改变

根据Promise规则,接着写刚刚创建的类:

const stateArr = ["pending", "fulfilled", "rejected"]; // 三种状态
/**
 *  @params {function} callback 需要执行的业务方法
 */
class MyPromise {
  constructor(callback) {
    this.state = stateArr[0]; // 当前状态
    this.value = null; // 完成时的返回值
    this.reason = null; // 失败原因

    callback(this.resolve, this.reject); // 调用此function
  }

  // callback中执行的resolve方法
  resolve = (value) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[1]; // 更新状态为 fulfilled
      this.value = value; // 写入最终的返回值
    }
  };

  // callback中执行的reject方法
  reject = (reason) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[2]; // 更新状态为 rejected
      this.reason = reason; // 写入拒绝的原因
    }
  };
}

测试一下: 可以看到,调用resolve后,状态变为fulfilled,再调用reject时,状态和值都不会改变,这样符合 Promise 规范~~

3.then 方法

我们的MyPromise写到这里,他已经可以实现更新状态和传值了,但是它的值怎么样输出给我们的业务呢?
Promise的使用可以看到,它是通过then方法来输出值的。then是是一个必要的方法,看一下then的规范:

  • promise 必须提供一个then方法去访问他的当前或最终的值或原因
  • promise 中then方法接收两个参数 onFulilledonRejected

下面是关于onFulilledonRejected的规范(部分)

  • onFulilledonRejected两者都是一个可选的参数:
    • 如果onFulilled不是一个函数,它必须被忽视
    • 如果onRejected不是一个函数,它必须被忽视
  • 如果onFulilled是一个函数:
    • 它必须在 fulfilled 时被调用,promise 方法中的value作为第一个参数
    • 它必须在 fulfilled 之前被调用
    • 它不能被多次调用
  • 如果onRejected是一个函数:
    • 它必须在 rejected 时被调用,promise 方法中的reason作为第一个参数
    • 它必须在 rejected 之前被调用
    • 它不能被多次调用
  • 在执行上下文堆栈仅包含平台代码之前,不能调用onFulfilledonRejected
  • onFulfilledonRejected必须是一个函数
  • then可以在同一个 promise 中多次被调用
  • then 必须返回一个promise

根据 then 函数的规则,我们来设计这个 then 方法

const stateArr = ["pending", "fulfilled", "rejected"]; // 三种状态
class MyPromise {
  constructor(callback) {
    this.state = stateArr[0]; // 当前状态
    this.value = null; // 完成时的返回值
    this.reason = null; // 失败原因

    callback(this.resolve, this.reject); // 调用此function
  }

  // callback中执行的resolve方法
  resolve = (value) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[1]; // 更新状态为 fulfilled
      this.value = value; // 写入最终的返回值
    }
  };

  // callback中执行的reject方法
  reject = (reason) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[2]; // 更新状态为 rejected
      this.reason = reason; // 写入拒绝的原因
    }
  };

  // then方法
  then = (onFulilled, onRejected) => {
    // 判断onFulilled 和 onRejected是否是一个函数,如果不是函数则忽略它
    onFulilled =
      typeof onFulilled === "function" ? onFulilled : (value) => value;
    onRejected =
      typeof onRejected === "function" ? onRejected : (reason) => reason;

    // 如果状态是fulfilled
    if (this.state === stateArr[1]) {
      // then返回的必须是一个promise
      return new MyPromise((resolve, reject) => {
        try {
          const result = onFulilled(this.value); // 执行传入的onFulilled方法

          // 如果onFulilled返回的是一个Promise,则调用then方法
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      });
    }

    // 如果状态是rejected
    if (this.state === stateArr[2]) {
      // then返回的必须是一个promise
      return new MyPromise((resolve, reject) => {
        try {
          const result = onRejected(this.reason); // 执行传入的onRejected方法

          // 如果onRejected返回的是一个Promise,则调用then方法
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      });
    }
  };
}

测试一下:

成功返回:

失败返回:

4.继续完善

至此,我们的MyPromise的已经基本可以运行了,但是现在有一个很严重的缺陷,如果遇到异步的请求时候,resolve不能按上下文执行,这会导致 then 方法执行失败例如

var test = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(123);
  }, 2000);
}).then((msg) => {
  console.log(msg);
  return 456;
});

因为在调用then方法的时候,promise的状态还没有改变,而我们的then方法还没有处理 pending 状态的逻辑。 这导致执行异步方法的时候,then 方法不会返回任何东西
比如,在上面的例子中,javscript 已经把then方法执行了,但setTimeout中的方法还在eventloop中等待执行。 这样需要做的是:

  • then中的方法保存起来,等待resolvereject执行后再调用刚刚保存的then中的方法
  • 由于在这期间可能会有多个then方法会被执行,所以需要用一个数据来保存这些方法
    根据这两点,再来修改一些代码
    // 在constructor中新增两个数组分别用于装then中的resolve和reject方法
    constructor(callback) {
        this.resolveArr = [];
        this.rejectArr = [];
    }

    // 修改resolve方法
    resolve = (value) => {
        // 判断状态是否需要是pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; // 更新状态为 fulfilled
               this.value = value; // 写入最终的返回值

               this.resolveArr.forEach(fun => fun(value)) // 循环执行then已插入的resolve方法
            }
    }

    // 修改reject方法
    reject = (reason) => {
        // 判断状态是否需要是pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; // 更新状态为 fulfilled
               this.reason = reason; // 写入最终的返回值

               this.rejectArr.forEach(fun => fun(reason)) // 循环执行then已插入的reject方法
            }
    }

    // then方法中需要添加捕捉pending状态的逻辑
    then = (onFulilled, onRejected) => {
        // 如果状态为pending
        if (this.state === stateArr[0]) {
            return new Promise((resolve, reject) => {
                // 插入成功时调用的函数
                this.resolveArr.push((value) => {
                    try {
                        const result = onFulilled(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else {
                            resolve(result);
                        }
                    } catch(err) {
                        reject(err);
                    }
                })

                // 插入失败时调用的函数
                this.rejectArr.push((value) => {
                    try {
                        const result = onRejected(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else {
                            resolve(result);
                        }
                    } catch(err) {
                        reject(err)
                    }
                })
            })
        }
    }

写好了,测试一下~

new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(123);
  }, 2000);
})
  .then((msg) => {
    console.log(msg);
    return new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(456);
      }, 2000);
    });
  })
  .then((msg) => {
    console.log(msg);
  });

5.Promise 的其他方法

根据 Promise 规范实现的Promise大致已经完成啦,最后我们把 Promise 中实现的方法也补一下

  • catch 方法
// 在MyPromise原型中实现
class MyPromise {
  // 调用then中的reject
  catch = (reject) => {
    this.then(null, reject);
  };
}
  • resolve
MyPromise.resolve = (value) => {
  return new MyPromise((resolve, reject) => {
    resolve(value);
  });
};
  • reject
MyPromise.resolve = (reason) => {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};
  • 还有allracefinally(原型方法),其实都是根据Promise 中的原型方法和Promise 规则实现的,这里就不一一列举啦。需要了解的小伙伴可以自行去看

最终代码(源码地址)

const stateArr = ["pending", "fulfilled", "rejected"]; // 三种状态
class MyPromise {
  constructor(callback) {
    this.state = stateArr[0]; // 当前状态
    this.value = null; // 完成时的返回值
    this.reason = null; // 失败原因
    this.resolveArr = [];
    this.rejectArr = [];

    callback(this.resolve, this.reject); // 调用此function
  }

  // callback中执行的resolve方法
  resolve = (value) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[1]; // 更新状态为 fulfilled
      this.value = value; // 写入最终的返回值

      this.resolveArr.forEach((fun) => fun(value)); // 循环执行then已插入的resolve方法
    }
  };

  // callback中执行的reject方法
  reject = (reason) => {
    // 判断状态是否需要是pending
    if (this.state === stateArr[0]) {
      this.state = stateArr[1]; // 更新状态为 fulfilled
      this.reason = reason; // 写入最终的返回值

      this.rejectArr.forEach((fun) => fun(reason)); // 循环执行then已插入的reject方法
    }
  };

  // then方法
  then = (onFulilled, onRejected) => {
    // 判断onFulilled 和 onRejected是否是一个函数,如果不是函数则忽略它
    onFulilled =
      typeof onFulilled === "function" ? onFulilled : (value) => value;
    onRejected =
      typeof onRejected === "function" ? onRejected : (reason) => reason;

    // 如果状态为pending
    if (this.state === stateArr[0]) {
      return new MyPromise((resolve, reject) => {
        // 插入成功时调用的函数
        this.resolveArr.push((value) => {
          try {
            const result = onFulilled(value);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (err) {
            reject(err);
          }
        });

        // 插入失败时调用的函数
        this.rejectArr.push((value) => {
          try {
            const result = onRejected(value);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (err) {
            reject(err);
          }
        });
      });
    }

    // 如果状态是fulfilled
    if (this.state === stateArr[1]) {
      // then返回的必须是一个promise
      return new MyPromise((resolve, reject) => {
        try {
          const result = onFulilled(this.value); // 执行传入的onFulilled方法

          // 如果onFulilled返回的是一个Promise,则调用then方法
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      });
    }

    // 如果状态是rejected
    if (this.state === stateArr[2]) {
      // then返回的必须是一个promise
      return new MyPromise((resolve, reject) => {
        try {
          const result = onRejected(this.reason); // 执行传入的onRejected方法

          // 如果onRejected返回的是一个Promise,则调用then方法
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      });
    }
  };

  // 调用then中的reject
  catch = (reject) => {
    this.then(null, reject);
  };
}

MyPromise.resolve = (value) => {
  return new MyPromise((resolve, reject) => {
    resolve(value);
  });
};

MyPromise.resolve = (reason) => {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};

小结

这次我们了解了 promise 是如何实现的:

  • 必须是构造函数
  • 三种状态(pending,resolve,reject)
  • then 方法(promise 中必须要有的方法)

从构造函数开始,到三种状态的实现,最后实现 then 方法一步步根据Promise规则来实现 Promise。了解完以后就可以在面试官面前手写一个 Promise 啦!😄