手撕 Promise

72 阅读12分钟

手撕 Promise

为了更深刻的理解 Promise 核心概念与执行流程 , 我们将从零开始实现一个完整的 Promise对象 , 命名为 MyPromise , 为了确保在不支持原生 Promise 的环境中也能使用 , 我们采用 ES5 编程 。


MyPromsie 在本文中的实现知识点合集

一、定义MyPromise构造函数 :

  • 接收一个执行器函数 fn(resolve, reject)
  • 初始化 Promise 的回调函数是同步执行的 , 所以fn直接调用立即执行
  • 通过 promiseStatepromiseResult 属性来跟踪 MyPromise的状态和结果
  • resolve用于改变 MyPromise的状态和值 , 将pending状态改为fulfilled状态 ,并触发thenCallBack的调用
  • reject 用于改变 MyPromise的状态和值 , 将pending状态改为rejected状态 ; 并触发catchCallBack的调用

二、静态方法 resolvereject

  • resolve 用于创建一个已成功的 MyPromise实例
  • reject 用于创建一个已失败的 MyPromise实例

三、MyPromise.prototype.then

  • 用于注册成功的回调函数
  • 实现链式调用 ,并将每次回调结果传递到下一个 then 方法中

四、MyPromise.prototype.catch

  • 用于注册失败的回调函数
  • 实现链式调用,跨对象的 catch捕捉流程 , 以及链式调用的中断处理

五、MyPromise 并发的方法实现

  • MyPromise.all 方法 :用于等待所有的 MyPromise 完成 ,返回一个新的MyPromise 实例

  • MyPromise.race 方法 :用于等待第一个 MyPromise 完成 ,返回一个新的MyPromise 实例

一、定义MyPromise构造函数

MyPromise 对象的基本结构定义

定义 promiseStatepromiseResult 来记录MyPromise的状态和值 ;

回调函数fn具有两个参数 resolve reject

初始化 MyPromise 的回调函数是同步执行的 , 所以fn直接调用

function MyPromise(fn) {
  // promise 的状态
  this.promiseState = "pending";
  // promise 的值
  this.promiseResult = undefined;
  var resolve = function (value) {};
  var reject = function (errValue) {};
  if (fn) {
    fn(resolve, reject);
  } else {
    throw "Init Error,Please use a function to init MyPromise!";
  }
}

在调⽤resolvereject时,修改MyPromise对象的状态和值 ; 初始状态是 pending ,可变成fulfilledrejected其中之⼀

//保存上下⽂对象
var _this = this;
var resolve = function (value) {
  if (_this.promiseState == "pending") {
    _this.promiseState = "fulfilled";
    _this.promiseResult = value;
  }
};
var reject = function (errValue) {
  if (_this.promiseState == "pending") {
    _this.promiseState = "rejected";
    _this.promiseResult = errValue;
  }
};

在初始化结构以后 , 我们尝试实例化 MyPromise

var p = new MyPromise(function (resolve, reject) {
  console.log("MyPromise 实例化执行了");
  resolve("成功");
});
console.log(p);

输出结果如下:

MyPromise 实例化执行了
MyPromise {
    promiseResult:"成功"
    promiseState:"fulfilled"
}

执行流程分析 :

  • 实例化 MyPromise 时 , 传入了一个执行器函数 fn 作为参数 ;

  • 构造函数 MyPromise(fn) 接收了这个执行器函数 fn 并立即执行它 ;

  • 执行器函数 fn 中调用了resolve("成功") , 这会导致 resolve 函数被调用 , 并将状态从 pending 变更为 fulfilled,同时设置 promiseResult 为"成功" ;

其内容如下:

立即执行 fn(resolve, reject)代码 :

等同于如下代码:
(function (resolve, reject) {
  console.log("MyPromise 实例化执行中...");
  resolve("成功");
})(resolve, reject);

二、静态方法 resolve 和 reject

MyPromise上扩展一个 resolve 方法 , 可以通过MyPromise.resolve简写声明一个状态为fulfilledMyPromise对象 ;

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

MyPromise上扩展一个reject 方法 , 可以通过MyPromise.reject简写声明一个状态为rejectMyPromise对象 ;

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

三、MyPromise.prototype.then

1. 调用 resolve 触发 thenCallBack

MyPromise方法扩充 :

  • 定义一个函数对象thenCallback , 用来保存then中的callback

  • 因为thenCallback是需要异步执行的,此处使用setTimeout来模拟异步操作 ;

  • 将 resolve 接收的参数 value 直接传递给 thenCallback ;

// 定义一个函数对象,用来保存then中的callback
this.thenCallback = undefined;

var resolve = function (value) {
  if (_this.promiseState == "pending") {
    _this.promiseState = "fulfilled";
    _this.promiseResult = value;
    // 使用setTimeout来模拟异步操作
    setTimeout(function () {
      if (_this.thenCallback) {
        _this.thenCallback(value);
      }
    });
  }
};

2. then触发时给 thenCallback 传值

  • 接受一个回调函数 callBack 做参数
  • callback 封装后赋值给 thenCallback
MyPromise.prototype.then = function (callback) {
  this.thenCallback = function (value) {
    callback(value);
  };
};

案例一 : ( resolve 同步调用 )

var p = new MyPromise(function (resolve, reject) {
  resolve("success");
});
p.then(function (res) {
  console.log("then1执行-----", res);
});

resolve 同步调用 - 执行流程分析 :

  • 实例化 MyPromise(fn) , 自动触发 fn 执行 ;

  • 调用 resolve("成功") , 改变 MyPromise状态和值 ; 此时thenCallback还未赋值 , 但是 setTimeout 将其放入了任务队列 ;

  • 调用 p.then(callback) , 触发原型上的 then , 将 callback 封装后赋值给 thenCallback ;

  • 同步代码执行完毕 , 进入事件循环 , thenCallback 从任务队列中取出来并执行 , 将结果值value传递给 callback ;

案例二 : ( resolve 异步调用 )

var p = new MyPromise(function (resolve, reject) {
  setTimeout(() => {
    resolve("success");
  }, 1000);
});
p.then(function (res) {
  console.log("then1执行-----", res);
});

resolve 异步调用 - 执行流程分析 :

  • 实例化 MyPromise(fn) , 自动触发 fn 执行 , 并设置一秒钟的定时器 ;

  • 调用 p.then(callback) , 触发原型上的 then , 将 callback 赋值给 thenCallback ;

  • 同步代码执行完毕 , 进入事件循环 , 一秒钟后 , 调用 resolve("成功") , 改变MyPromise状态和值 , setTimeoutthenCallback 放入任务队列 ;

  • 事件循环继续执行列表中的任务 , 取出 thenCallback 执行 , 将结果值value传递给 callback ;

3. then 链式调用

上述代码已经实现了调用resolve自动触发 then的功能 ,但还不具有链式调用的功能 ; 我们需要进一步增强 MyPromise 的流程控制代码,以实现链式调用,并确保每次的结果能够顺利向下传递

MyPromise.prototype.then方法扩充

  1. 返回一个新的 MyPromise 实例 , 用于处理当前 then 方法的回调结果 ;

  2. 设置 thenCallback : 在新实例的函数执行器中 , 设置当前实例的thenCallback为新函数 , 并传入当前实例的值 ;

  3. 执行回调函数并传递值 :当 thenCallback 被调用时, 执行传入 then 方法的回调函数 callBack , 并将当前实例的值传递给 callBack , callBack 的返回值 res 通过 resolve(res) 传递给新实例 ;

  4. 实现链式调用:then 方法每次都会返回一个新实例 , 可以在其上继续调用 then 方法,实现链式调用 , 并将每次回调结果传递到下一个 then 方法中 ;

MyPromise.prototype.then = function (callBack) {
  var _this = this;
  // 1. 返回一个新的 MyPromise实例
  return new MyPromise(function (resolve, reject) {
    // 2. 设置 thenCallBack
    _this.thenCallback = function (value) {
      // 3. 执行回调函数并传递值
      var res = callBack(value);
      resolve(res);
    };
  });
};

链式调用案例 :

var p = new MyPromise(function (resolve, reject) {
  resolve("success");
});
p.then(function (res) {
  console.log("then1执行", res);
  return "123";
}).then(function (res) {
  console.log("then2执行", res);
});

执行流程分析 :

  1. 实例化 MyPromise : 创建实例 p , 自动触发fn执行 , 调用 resolve("成功") , p 的状态变为 fulfilled , 值为"成功" , 将 p.thenCallback 被放入任务队列

  2. 调用 p.then(callback1)

  • 触发原型上的 p.then 方法

  • 返回新的 MyPromsie 实例 p1 , p1fn 会立即执行 , 给 _this.thenCallback 被赋值为一个新的函数 ;

_this.thenCallback = function (value) {
      var res = callBack(value);
      resolve(res);
};
  • p.thenCallback 从任务队列中取出来执行
// 调用 callBack1 , 输出 "then1 执行 成功" , res = "123"
var res = callBack(value);
// 调用 p1.resolve(res) , p1 的状态变为 fulfilled , 值为 "123"
// 并将 p1.thenCallback 被放入任务队列 , 但是并没有 p1.then , 所有用于也不会触发 
resolve(res);
  1. 调用 p.then(callback2)

  • 触发原型上的 p.then 方法
  • 返回一个新的 MyPromise 实例 p2 , p2fn 会立即执行 , 给 _this.thenCallback 被赋值为一个新的函数 ;
_this.thenCallback = function (value) {
      var res = callBack(value);
      resolve(res);
};
  • p.thenCallback 从任务队列中取出来执行
// 调用 callBack2 , 输出 "then2 执行 123" , res="undefined"(因为 callback2 没有返回值)
resolve(res);
// 调用 p2.resolve(res),p2 的状态变为 fulfilled,值为 undefined
var res = callBack(value);

4. 返回值是 MyPromise对象的处理

then 方法中,如果检测到 callBack 方法返回值新的 MyPromise 对象时 , 直接调用返回值的 then 方法 , 并将最终结果传递给当前实例的 thenCallback

resolve 方法扩充

resolve 方法中添加判断 , 如果是 MyPromise 对象 , 则调用 then 方法

  var resolve = function (value) {
     // 更改 promise的 对象和值
     if (_this.promiseState == "pending") {
       _this.promiseState = "fulfilled";
       _this.promiseResult = value;
       // 如果检测到callback中返回值的类型为MyPromise对象时,直接调用返回值的then方法
       if (value instanceof MyPromise) {
         value.then(function (res) {
           if (_this.thenCallback) {
             _this.thenCallback(res);
           }
         });
       } else {
         setTimeout(function () {
           if (_this.thenCallback) {
             _this.thenCallback(value);
           }
         });
     }
  }

返回值是MyPromise对象的链式调用案例 :

var p = new MyPromise(function (resolve, reject) {
  resolve(0);
});
p.then(function (res) {
  console.log("then2执行", res);
  return MyPromise.resolve("2");
}).then(function (res) {
  console.log("then3执行", res);
});

四. MyPromise.prototype.catch

1. 调用 reject 自动触发 catchCallBack

MyPromise 对象调用 reject 函数时,MyPromise 的状态由 pending 变为 rejectedpromiseResult 保存拒绝的原因 。 如果定义了 catch 方法 ,则调用 catch 方法,并将拒绝的原因传递给 catch 方法的回调函数 , 如果没定义 catch , 则抛出异常。

MyPromise方法扩充

....
// 定义一个函数对象,用来注册catch中的callback
this.catchCallBack = undefined;
var reject = function (errValue) {
  if (_this.promiseState == "pending") {
    _this.promiseState = "rejected";
    _this.promiseResult = errValue;
    setTimeout(function () {
      if (_this.catchCallBack) {
        _this.catchCallBack(errValue);
      } else {
        throw "catch is not defined";
      }
    });
  }
};

2. catch 触发时给 catchCallback 传值并实现链式调用

MyPromise.prototype.catch = function (callBack) {
  var _this = this;
  return new MyPromise(function (resolve, reject) {
    _this.catchCallBack = function (errValue) {
      var res = callBack(errValue);
      resolve(res);
    };
  });
};

通过 reject 方法触发 catch 的 基础示例 :

var p = new MyPromise(function (resolve, reject) {
  reject("error");
});
p.catch(function (err) {
  console.log("catch执行", err);
  return "123";
});

到目前为止 , 通过reject 触发单个 catch方法的回调已经实现 ! 但是实际情况更为复杂 !!!

3. 实现跨对象的 catch 捕捉流程

上面的案例中 , 已经可以执行 MyPromisecatch 函数了 。但是 , 如果改为以下代码 , 将会发现 catch 函数不会执行 。

var p = new MyPromise(function (resolve, reject) {
  reject("error");
});
console.log(p);
p.then(function (res) {
  console.log(res);
}).catch(function (err) {
  console.log(err);
});

这是因为我们目前编写的代码流程是 : 当MyPromsie的状态变成为 rejected 后 , 如果判断没有 catch 回调 , 将无法注册 , 导致流程中断了 。

实际应用场景是 :使用链式调用时 , 第一个调用的可能是 then 而不是 catch , 为了确保 catch 能够捕获错误,我们需要修改代码逻辑,使 catch 信息能够 向下传递

reject 方法扩充

当触发 reject 时 , 判断如果没有 catchCallBack , 那就去触发 thenCallback

var reject = function (errValue) {
  if (_this.promiseState == "pending") {
    _this.promiseState = "rejected";
    _this.promiseResult = errValue;
    setTimeout(function () {
      if (_this.catchCallBack) {
        _this.catchCallBack(errValue);
      } else if (_this.thenCallback) {
        _this.thenCallback(errValue);
      } else {
        throw "catch is not defined";
      }
    });
  }
};

MyPromise.prototype.then 扩充

当我们检测到触发thenCallback的对象是 rejected 时 , 就继续调用下一个对象的 reject

MyPromise.prototype.then = function (callBack) {
  var _this = this;
  return new MyPromise(function (resolve, reject) {
    _this.thenCallback = function (value) {
      if (_this.promiseState == "rejected") {
        reject(value);
      } else {
        var res = callBack(value);
        resolve(res);
      }
    };
  });
};

链式传递 catch 信息案例 :

var p = new MyPromise(function (resolve, reject) {
  reject("error");
});
p.then(function (res) {
  console.log("then执行", res);
  return "123";
}).catch(function (err) {
  console.log("catch执行", err);
});

4. 链式调用中断后的异常捕获

当在 then 函数的链式调用中 , 抛出 MyPromise.reject() , 后面链式中的 catch改如何捕获到呢 ?

MyPromise.prototype.then 扩充

加上判断 , 处理在链式调用中 return MyPromise.reject()的情况

MyPromise.prototype.then = function (callBack) {
  var _this = this;
  return new MyPromise(function (resolve, reject) {
    _this.thenCallback = function (value) {
      if (_this.promiseState == "rejected") {
        reject(value);
      } else {
        var res = callBack(value);
        //加上判断 , 处理 return MyPromise.reject("中断MyPromise")的情况
        if (res instanceof MyPromise && res.promiseState == "rejected") {
          res.catch(function (errValue) {
            reject(errValue);
          });
        } else {
          resolve(res);
        }
      }
    };
  });
};

返回值是 MyPromise.reject 案例

var p = new MyPromise(function (resolve, reject) {
  resolve("success");
});
p.then(function (res) {
  console.log("then1---执行", res);
  return MyPromise.reject("中断MyPromise");
})
  .then(function (res) {
    console.log("then2---执行", res);
  })
  .catch(function (err) {
    console.log("catch执行", err);
  });
console.log(p);

五.MyPromise 并发的方法实现

封装 MyPromise.all

接受一个 MyPromise 实例数组作为参数 , 当数组中所有 MyPromise 实例均成功解决时,该方法返回一个包含所有成功结果的数组 。若数组中任何 MyPromise 实例遭遇拒绝,则立即转发第一个错误,终止其余 MyPromise 的执行并抛出该错误信息。

MyPromise.all = function (promiseArr) {
  let resArr = [];
  return new MyPromise(function (resolve, reject) {
    promiseArr.forEach((item, index) => {
      item
        .then(function (res) {
          resArr[index] = res;
          var allResolve = promiseArr.every(
            (_item) => _item.promiseState == "fulfilled"
          );
          if (allResolve) {
            resolve(resArr);
          }
        })
        .catch(function (err) {
          reject(err);
        });
    });
  });
};

MyPromsie.all 使用案例 :

let p1 = new MyPromise(function (resolve, reject) {
  setTimeout(function () {
    resolve("第一个MyPromsie执行完毕!");
  }, 1000);
});
let p2 = new MyPromise(function (resolve, reject) {
  setTimeout(function () {
    resolve("第二个MyPromsie执行完毕!");
  }, 1500);
});
let p3 = new MyPromise(function (resolve, reject) {
  setTimeout(function () {
    reject("第三个MyPromsie执行失败!");
  }, 2000);
});

MyPromise.all([p1, p2, p3])
  .then(function (res) {
    console.log(res);
  })
  .catch(function (err) {
    console.error(err);
  });
封装 MyPromise.race

接受一个 MyPromise 实例数组作为参数 , 一旦数组中任意一个 MyPromise 率先完成 , 就返回那个率先完成 的 MyPromise 的结果 , 体现“竞赛”特性

MyPromise.race = function (promiseArr) {
  return new MyPromise(function (resolve, reject) {
    promiseArr.forEach(function (item, index) {
      item
        .then(function (res) {
          resolve(res);
        })
        .catch(function (err) {
          reject(err);
        });
    });
  });
};

MyPromise.race 使用案例 :

MyPromise.race([p1, p2, p3])
  .then(function (res) {
    console.log(res);
  })
  .catch(function (err) {
    console.error(err);
  });

六.源代码总结

/**
 * 自定义的 MyPromise 类
 * @param {Function} fn - 初始化函数,接受两个参数 resolve 和 reject
 */
function MyPromise(fn) {
  var _this = this;
  this.promiseResult = undefined;
  this.promiseState = "pending";
  this.thenCallback = undefined;
  this.catchCallback = undefined;
  /**
   * resolve 函数,用于将 MyPromise 状态变为 fulfilled
   * @param {*} value - 成功的值
   */
  var resolve = function (value) {
    if (_this.promiseState == "pending") {
      _this.promiseResult = value;
      _this.promiseState = "fulfilled";
      if (value instanceof MyPromise) {
        if (_this.thenCallback) {
          value.then(function (res) {
            _this.thenCallback(res);
          });
        }
      } else {
        setTimeout(function () {
          if (_this.thenCallback) {
            _this.thenCallback(value);
          }
        });
      }
    }
  };

  /**
   * reject 函数,用于将 MyPromise 状态变为 rejected
   * @param {*} errValue - 拒绝的原因
   */
  var reject = function (err) {
    if (_this.promiseState == "pending") {
      _this.promiseResult = err;
      _this.promiseState = "rejected";
      setTimeout(function () {
        if (_this.catchCallback) {
          _this.catchCallback(err);
        } else if (_this.thenCallback) {
          _this.thenCallback(err);
        } else {
          throw "this Promise was reject,but can not found catch!";
        }
      });
    }
  };
  if (fn) {
    fn(resolve, reject);
  } else {
    throw "Init Error,Please use a function to init MyPromise!";
  }
}

/**
 * 静态方法 resolve,用于创建一个已成功的 MyPromise 实例
 * @param {*} value - 成功的值
 * @returns {MyPromise} - 返回一个已成功的 MyPromise 实例
 */
MyPromise.resolve = function (value) {
  return new MyPromise(function (resolve) {
    resolve(value);
  });
};

/**
 * 静态方法 reject,用于创建一个已失败的 MyPromise 实例
 * @param {*} reason - 拒绝的原因
 * @returns {MyPromise} - 返回一个已失败的 MyPromise 实例
 */
MyPromise.reject = function (value) {
  return new MyPromise(function (resolve, reject) {
    reject(value);
  });
};

/**
 * then 方法,用于注册成功和失败的回调函数
 * @param {Function} callBack - 成功的回调函数
 * @returns {MyPromise} - 返回一个新的 MyPromise 实例
 */
MyPromise.prototype.then = function (callback) {
  var _this = this;
  return new MyPromise(function (resolve, reject) {
    _this.thenCallback = function (value) {
      if (_this.promiseState == "rejected") {
        reject(value);
      } else {
        var callbackRes = callback(value);
        if (callbackRes instanceof MyPromise) {
          if (callbackRes.promiseState == "rejected") {
            callbackRes.catch(function (errValue) {
              reject(errValue);
            });
          }
        } else {
          resolve(callbackRes);
        }
      }
    };
  });
};

/**
 * catch 方法,用于注册失败的回调函数
 * @param {Function} callback - 失败的回调函数
 * @returns {MyPromise} - 返回一个新的 MyPromise 实例
 */
MyPromise.prototype.catch = function (callback) {
  var _this = this;
  return new MyPromise(function (resolve, reject) {
    _this.catchCallback = function (errValue) {
      var callbackRes = callback(errValue);
      resolve(callbackRes);
    };
  });
};

/**
 * MyPromise.all 方法,用于等待所有 Promise 完成
 * @param {Array<Promise>} promiseArr - 包含多个 Promise 的数组
 * @returns {MyPromise} - 返回一个新的 MyPromise 实例
 */
MyPromise.all = function (promiseArr) {
  var resArr = [];
  var errValue = undefined;
  var isRejected = false;
  return new MyPromise(function (resolve, reject) {
    for (var i = 0; i < promiseArr.length; i++) {
      (function (i) {
        promiseArr[i]
          .then(function (res) {
            resArr[i] = res;
            let r = promiseArr.every((item) => {
              return item.promiseState == "fulfilled";
            });
            if (r) {
              resolve(resArr);
            }
          })
          .catch(function (err) {
            isRejected = true;
            errValue = err;
            reject(err);
          });
      })(i);
      if (isRejected) {
        break;
      }
    }
  });
};

/**
 * MyPromise.race 方法,用于等待第一个完成的 Promise
 * @param {Array<Promise>} promiseArr - 包含多个 Promise 的数组
 * @returns {MyPromise} - 返回一个新的 MyPromise 实例
 */
MyPromise.race = function (promiseArr) {
  var end = false;
  return new MyPromise(function (resolve, reject) {
    for (var i = 0; i < promiseArr.length; i++) {
      (function (i) {
        promiseArr[i]
          .then(function (res) {
            if (end == false) {
              end = true;
              resolve(res);
            }
          })
          .catch(function (err) {
            if (end == false) {
              end = true;
              reject(err);
            }
          });
      })(i);
    }
  });
};