手写Promise记录

654 阅读9分钟

前言

最近复盘面试知识点,问到异步编程的解决方案这一块,发现自己的认识并不清晰,很容易被问倒。本文主要记录了自己实现一个promise的全过程,建议大家配合Promise A+规范一起阅读。

Promise? what & why

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。ES6规范提出Promise对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者(语义化更好的aysnc/await只是promise和generator的语法糖。async-await是建立在 promise机制之上的)

Promise的基本结构

Promise构造函数接受一个函数作为参数,我们称该函数为excuterexcuter又包含resolve和reject两个参数,它们是两个函数。promise初始化的时候就会执行这个函数,

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("resolve");
  }, 1000);
});

Promise基本状态与值

一个 Promise 必然处于以下几种状态之一:

  1. 待定(pending): 初始状态,在此状态下可以落定 (settled) 为 fulfilled 或 rejected状态。
  2. 已兑现(fulfilled): 意味着操作成功完成,返回一个私有的值value
  3. 已拒绝(rejected): 意味着操作失败,返回一个私有的值reason

状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。

const PENDING = "Pending"; //等待
const FULLFILLED = "Fullfilled"; //执行
const REJECTED = "Rejected"; //拒绝
class myPromise {
  constructor(excuter) {
    if (typeof excuter !== "function") {
      throw new Error("myPromise must accept a function");
    }
    this.state = PENDING;
    this.reason = undefined;
    this.value = undefined;
    try {
      excuter(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }
  // Promise类还具有静态方法 resolve 避免重名
  _resolve(val) {
    if (this.state !== PENDING) return;
    this.state = FULLFILLED;
    this.value = val;
  }

  _reject(err) {
    if (this.state !== PENDING) return;
    this.state = REJECTED;
    this.reason = err;
  }
}

最重要的then方法

提供一个 then 方法来访问当前或最终的 value 或 reason,then方法需要满足:

  1. 接收两个函数作为参数,参数可选
  2. 当参数不为函数时会被忽略(2.2.1)
  3. 两个函数都是异步执行,会放入事件队列等待下一轮 tick
  4. 当调用 onFulfilled 函数时,会将当前 Promise 的 value 值作为参数传入。
  5. 当调用 onRejected 函数时,会将当前 Promise 的 reason 失败原因作为参数传入。
  6. onFulfilled 与 onRejected调用次数不可超过一次,状态改变前其不可被调用
  7. then 函数的返回值为 Promise,这是then可以链式调用的原因
  8. then 可以被同一个 Promise 多次调用

根据promise a+规范: then 方法可以被同一个 promise 对象调用多次

  1. 当 promise 成功状态时,所有 onFulfilled 需按照其注册顺序依次回调
  2. 当 promise 失败状态时,所有 onRejected 需按照其注册顺序依次回调

参照上面这些规则,我们可以完善我们的myPromise,首先需要支持多次调用,这点可以用两个数组onFullfilledQueues,onRejectedQueues来维护,通过在then方法注册的时候将回调添加到队列中,等待状态改变执行。

 constructor(excuter) {
    if (typeof excuter !== "function") {
      throw new Error("myPromise must accept a function");
    }
    this.state = PENDING;
    this.reason = undefined;
    this.value = undefined;

    // 添加成功回调函数队列
    this.onFullfilledQueues = [];

    // 添加失败回调函数队列
    this.onRejectedQueues = [];
    try {
      excuter(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }

实现then方法,当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去... 下面参考promise解决程序(2.3)实现一个解决过程:

// Promise 解决过程2.3
  resolvePromise(promise, x, resolve, reject) {
    // 2.3.1
    if (promise === x) {
      return reject(new TypeError("error found in: promise A+ 2.3.1"));
    }
    //2.3.2
    if (x instanceof myPromise) {
      if (x.state === FULLFILLED) {
        resolve(x.value);
      } else if (x.state === REJECTED) {
        reject(x.reason);
      } else {
        x.then((y) => {
          this.resolvePromise(promise, y, resolve, reject);
        }, reject);
      }
    } else if (//2.3.3
      x !== null &&
      (typeof x === "object" || typeof x === "function")
    ) {
      var executed;
      try {
        var then = x.then;
        if (typeof then === "function") {
          then.call(
            x,
            (y) => {
              if (executed) return;
              executed = true;
              this.resolvePromise(promise, y, resolve, reject);
            },
            (e) => {
              if (executed) return;
              executed = true;
              reject(e);
            }
          );
        } else {
          resolve(x);
        }
      } catch (e) {
        if (executed) return;
        executed = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  }

根据上文中 then 方法的规则,我们知道返回的新的 Promise 对象的状态依赖于当前 then 方法回调函数执行的情况以及返回值,我们举几个场景说明:

  1. 如果 onFulfilled 或者 onRejected 返回一个值 x,
    • 若 x 不为 Promise ,则使 x 直接作为新返回的 Promise 对象的值 -- 见第四点
    • 若 x 为 Promise ,这时后一个回调函数,就会等待该 Promise 对象(即 x )的状态发生变化,才会被调用,并且新的 Promise 状态和 x 的状态相同。
//1.非promise
let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res => {
  // 返回一个普通值
  return '这里返回一个普通值'
})
promise2.then(res => {
  console.log(res) //1秒后打印出:这里返回一个普通值
})

//2. promise
let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res => {
  // 返回一个Promise对象
  return new Promise((resolve, reject) => {
    setTimeout(() => {
     resolve('这里返回一个Promise')
    }, 2000)
  })
})
promise2.then(res => {
  console.log(res) //3秒后打印出:这里返回一个Promise
})
  1. 如果 onFulfilled 或者onRejected 抛出一个异常 e ,则promise2必须变为失败(Rejected),并返回失败的值 e
let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res => {
  // 返回一个Promise对象
  return new Promise((resolve, reject) => {
    setTimeout(() => {
     resolve('这里返回一个Promise')
    }, 2000)
  })
})
promise2.then(res => {
  console.log(res) //3秒后打印出:这里返回一个Promise
})
  1. 如果onFulfilled 不是函数且 promise1 状态为成功(Fulfilled), promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值 -- 规范第二点,当参数不为函数时会被忽略
  2. 如果 onRejected 不是函数且 promise1 状态为失败(Rejected),promise2必须变为失败(Rejected) 并返回 promise1 失败的值

根据上述规则以及解决过程完善then方法

//实现Promise的then方法
  then(onFullfilled, onRejected) {
    onFullfilled =
      typeof onFullfilled === "function"
        ? onFullfilled
        : function (x) {
            return x;
          };
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : function (e) {
            throw e;
          };
    // 返回一个新的Promise对象
    let promise = new myPromise((resolve, reject) => {
      switch (this.state) {
        case PENDING:
          this.onFullfilledQueues.push(() => {
            try {
              var x = onFullfilled(this.value);
              this.resolvePromise(promise, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
          this.onRejectedQueues.push(() => {
            try {
              var x = onRejected(this.reason);
              this.resolvePromise(promise, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
          break;
        case FULLFILLED:
          try {
            var x = onFullfilled(this.value);
            this.resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
          break;
        case REJECTED:
          try {
            var x = onRejected(this.reason);
            this.resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
          break;
      }
    });

    return promise;
  }

分析一下代码的执行:

  1. 实例化promsie的过程实例上会执行传入的函数,将类内部的resolve和reject 绑到形参
  2. 遇到then方法会将参数存到这个实例的执行队列上(之前的回调函数),then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的,then 方法可被同一个 promise 调用多次
  3. 明确一点: promise的状态只由他内部的resolve reject中改变
  4. Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行
  5. 当promise状态改变的时候 会执行队列里的函数 然后根据现在的状态决定下一个promise的状态(then返回那个),我们把then方法返回新promise的参数(resolve,reject)传给了解决过程resolvePromise,这样我们就可改变这个新promise的状态。这就是链式调用的原理。 6.resolve的执行: 对 resolve 中的值作了一个特殊的判断,判断 resolve 的值是否为 Promise实例,如果是 Promise 实例,那么就把当前 Promise 实例的状态改变接口重新注册到 resolve 的值对应的 Promise 的 onFulfilled 中,也就是说当前 Promise 实例的状态要依赖 resolve 的值的 Promise 实例的状态。

catch方法

其实就是只处理错误状态的then方法

// 添加catch方法
catch (onRejected) {
  return this.then(undefined, onRejected)
}

测试工具 promises-tests

使用方法:

  1. git clone
  2. 在文件中导出出测试的接口
  3. 修改测试命令 在package.json中测试自己的promise.js文件(根据自己的文件而定) "test": "promises-aplus-tests promise.js 跑完测试需要一点时间,此工具用例没有测试到resolve()中传入promsie的情况,我在童欧巴大佬的1.手写 Promise 全家桶讨论过这个issue,大佬的文章质量都很高,大家可以follow学习。 传送: 大佬github

完整代码

const PENDING = "Pending"; //等待
const FULLFILLED = "Fullfilled"; //执行
const REJECTED = "Rejected"; //拒绝

class myPromise {
  constructor(excuter) {
    if (typeof excuter !== "function") {
      throw new Error("myPromise must accept a function");
    }
    this.state = PENDING;
    this.reason = undefined;
    this.value = undefined;

    // 添加成功回调函数队列
    this.onFullfilledQueues = [];

    // 添加失败回调函数队列
    this.onRejectedQueues = [];
    try {
      excuter(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }

  //需要异步调用数组中的函数,这里使用 setTimeout 来模拟异步。
  _resolve(val) {
    // console.log(val);
    //2.3. Promise解决程序
    if (val instanceof myPromise) {
      return val.then(this._resolve.bind(this), this._reject.bind(this));
      //   return val.then(this._resolve, this._reject);
    }

    const run = () => {
      if (this.state !== PENDING) return;
      this.state = FULLFILLED;
      this.value = val;
      // 依次执行成功队列中的函数,并清空队列
      let cb;
      while ((cb = this.onFullfilledQueues.shift())) {
        cb(this.value);
      }
    };

    // 为了支持同步的Promise,这里采用异步调用
    setTimeout(run);
  }

  _reject(err) {
    const run = () => {
      if (this.state !== PENDING) return;
      this.state = REJECTED;
      this.reason = err;
      // 依次执行失败队列中的函数,并清空队列
      let cb;
      while ((cb = this.onRejectedQueues.shift())) {
        cb(err);
      }
    };
    setTimeout(run);
  }

  // Promise 解决过程2.3
  resolvePromise(promise, x, resolve, reject) {
    // console.log(promise, x);
    if (promise === x) {
      return reject(new TypeError("error found in: promise A+ 2.3.1"));
    }
    if (x instanceof myPromise) {
      if (x.state === FULLFILLED) {
        resolve(x.value);
      } else if (x.state === REJECTED) {
        reject(x.reason);
      } else {
        x.then((y) => {
          this.resolvePromise(promise, y, resolve, reject);
        }, reject);
      }
    } else if (
      x !== null &&
      (typeof x === "object" || typeof x === "function")
    ) {
      var executed;
      try {
        var then = x.then;
        if (typeof then === "function") {
          then.call(
            x,
            (y) => {
              if (executed) return;
              executed = true;
              this.resolvePromise(promise, y, resolve, reject);
            },
            (e) => {
              if (executed) return;
              executed = true;
              reject(e);
            }
          );
        } else {
          resolve(x);
        }
      } catch (e) {
        if (executed) return;
        executed = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  }

  //实现Promise的then方法
  then(onFullfilled, onRejected) {
    onFullfilled =
      typeof onFullfilled === "function"
        ? onFullfilled
        : function (x) {
            return x;
          };
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : function (e) {
            throw e;
          };
    // 返回一个新的Promise对象
    let promise = new myPromise((resolve, reject) => {
      switch (this.state) {
        case PENDING:
          this.onFullfilledQueues.push(() => {
            try {
              var x = onFullfilled(this.value);
              this.resolvePromise(promise, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
          this.onRejectedQueues.push(() => {
            try {
              var x = onRejected(this.reason);
              this.resolvePromise(promise, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
          break;
        case FULLFILLED:
          try {
            var x = onFullfilled(this.value);
            this.resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
          break;
        case REJECTED:
          try {
            var x = onRejected(this.reason);
            this.resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
          break;
      }
    });

    return promise;
  }
  
  // -----Promise类的静态方法-----
  static resolve(value) {
    if (value instanceof myPromise) {
      return value;
    }
    return new myPromise((resolve, reject) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new myPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static all(list) {
    return new myPromise((resolve, reject) => {
      let results = [];
      let count = 0;
      for (let index = 0; index < list.length; index++) {
        const p = list[index];
        this.resolve(p).then(
          (res) => {
            results[index] = res
            count++;
            if (count == list.length) resolve(results);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  static race(list) {
    return new myPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一个实例率先改变状态,新的myPromise的状态就跟着改变
        this.resolve(p).then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  //实现catch
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
  //实现finally
  finally(onDone) {
    return this.then(onDone, onDone);
  }
}

后记

代码中有疑问或者不对的地方欢迎各位批评指正,共同进步。求点赞三连QAQ

参考链接: