一步一步教你手撕Promise

一步一步手撕Promise

手撕Promise已经是一个非常经典的面试题了,没办法,面试官就喜欢和你吹逼造火箭。学完会:

  • 知道promise的执行流程
  • promise如何实现链式调用的
  • 可以自己手写一个自己的promise
  • 面试造火箭

建议多看几遍,理解流程,然后一步一步敲一下代码。

关于Promise

在手撕Promise之前,我们要先观察一下Promise对象,我们知道promise有3个状态:

  • pending
  • fulfilled
  • rejected

通过调用resolve()就会将状态从pending转为fulfilled;调用reject()就会将状态从pending转为rejected(),且状态的转换是不可逆的。

  var p1 = new Promise(function (resolve, reject) {
    console.log(resolve, reject);
  });
  console.log(p1);

  var p2 = new Promise(function (resolve, reject) {
    resolve("成功");
  });
  console.log(p2);

  var p3 = new Promise(function (resolve, reject) {
    reject("失败");
  });
  console.log(p3);
复制代码

image.png 我们先观察一下,各个状态的返回结果。

也可以发现,resolve和reject是2个函数。

总体结构

  function MyPromise(fn) {
    //promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
    this.promiseState = "pending";
    this.promiseValue = undefined;
    var _this = this;
    var resolve = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "fulfilled";
        _this.promiseValue = value;
      }
    };
    var reject = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "rejected";
        _this.promiseValue = value;
      }
    };
    if (fn) {
      fn(resolve, reject);
    } else {
      throw "Init Error,Please use a function to init MyPromise!";
    }
  }
  MyPromise.prototype.then = function (callback) {};
  MyPromise.prototype.catch = function (callback) {};
复制代码

我们知道Promise是一个构造函数,需要传入一个回调函数来进行实例化。否则就会报错。

image.png 所以上述代码中,我们定义了一个fn的参数,这个是将来需要传入的函数。如果这个函数不存在,我们就抛出一个异常。

其次,按照对象特性,Promise初始化的时候回调函数是同步执行的,并且该回调函数有2个参数,分别是resolve和reject,resolve和reject是两个函数。因此,我们判断fn函数存在时,就直接调用这个函数,并且传入resolve和reject。

但是resolve和reject哪里来呢?

于是我们就通过var定义了这2个函数,这2个函数的调用会更改Promise内部的状态,并且这2个函数有一个参数,参数传进去的值最后会成为promise实例的[[promiseResult]]的值。

因为这个是一个构造函数,所以我们定义了2个属性:promiseStatepromiseValue,new的时候会成为实例的属性。promise初始状态为pending,值为undefined。

我们将this赋值给一个_this的变量,因为我们需要拿到一个指向promise实例对象的this;同时给resolve和reject定义一个行参,我们在调用时才会传入实参。

我们知道,promise的状态是不可逆的,也就是说,它只能从pending转到fulfilled或者rejected。

因此,我们在resolve和reject内部需要做一个判断,只有promiseState状态为pending的时候,我们才可以转为fulfilled或者rejected,并且将参数的value赋值给promise的value即promiseValue。

image.png 我们观察一下Promise构造函数,发现它的catch和then方法都在原型对象上,于是我们给自己的Promise定义了自己的then方法和catch方法。

目前,resolve和reject的作用就是更改promise实例的内部value值和状态。

实现then方法

总体结构写好了我们先来看看我们运行后有什么效果。

  var p = new MyPromise(function (resolve, reject) {
    resolve(123);
  });
  console.log(p);
  p.then(function (res) {
    console.log(res);
  });
复制代码

image.png

resolve之后我们看了一下结果,发现状态和值都改变成功了,然后调用了.then方法,因为我们什么都没写,所以不会有任何的反应。

思路

我们先来想一想,之前我们在讲promise执行流程的时候有说过,promise的回调是同步执行的。

.then()方法是一个微任务,即异步任务。并且.then()中可以传入2个回调函数,第一个是成功的回调,第二个是失败的回调。我们先暂时忽略第二个。

.then()的执行时机是在resolve执行之后,并且需要获取到resolve的实参。

所以我们可以给MyPromise定义一个thenCallback的属性,默认为undefined,如果then执行了,这个属性将会被赋值为一个函数,这个函数有一个形参用来接收resolve()的value值(即实参),并且会将value作为.then接受到的回调函数的实参去执行。具体看如下代码:

实现

  function MyPromise(fn) {
    this.promiseState = "pending";
    this.promiseValue = undefined;
    var _this = this;
    // 定义了.thenCallback的属性,一开始时是undefined
    this.thenCallback = undefined;

    var resolve = function (value) { // value是一个形参
      if (_this.promiseState == "pending") {
        _this.promiseState = "fulfilled";
        _this.promiseValue = value;
        //异步的执⾏then函数中注册的回调函数
        setTimeout(function () {
          if (_this.thenCallback) {
          // 将value值给thenCallback,thenCallback会给.then()括号内的回调函数
            _this.thenCallback(value); 
          }
        });
      }
    };

    var reject = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "rejected";
        _this.promiseValue = value;
      }
    };
    if (fn) {
      fn(resolve, reject);
    } else {
      throw "Init Error,Please use a function to init MyPromise!";
    }
  }
  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象,thenCallback接受到一个函数,这个函数有一个形参,将会是resolve()的值,这个形参会给callback进行调用
    this.thenCallback = function (value) {
      callback(value);
    };
  };
  MyPromise.prototype.catch = function (callback) {};

  var p = new MyPromise(function (resolve, reject) {
    resolve(123);
  });
  console.log(p);
  p.then(function (res) {
    console.log(res);
  });
复制代码

image.png 我们可以看到.then正确执行了一次。


流程详解

上述代码中:

我们将Promise实例的信息都定义在了MyPromise构造函数内部,比如:状态、值等等。初始状态时pendding,值是undefined。

MyPromise原型中有.then的方法,上述代码的.then方法就只负责将.then(callback)的callback回调函数赋值给new MyPromise出的实例的thenCallback属性。

而MyPromise内部的resolve执行时,有一个setTimeout定时器,他会把它的回调函数挂起,等待同步代码执行完毕后再去执行它的回调。

所以实际上的执行顺序是:

  • resolve()执行,把promiseState变成了fulfilled,promiseValue变成了123。

  • 然后执行到定时器,定时器的回调函数挂起。

  • 再console.log(p)

  • 执行.then函数,传入了function(res)...回调函数,这时候会将thenCallback属性从undefined变成了传入的function(res)...回调函数。

  • 这时候主程序的代码执行完了,开始执行挂起的异步代码,然后将123传给thenCallback,实际上就是传给function(res)...

  • 最后打印123

then的异步链式调用

目前我们已经实现了单个.then的调用了,但是原先的Promise可以通过.then来实现链式调用,而我们再次使用.then的时候,直接就报错了。

image.png

首先我们先观察一下原先的Promise调用.then之后返回了什么?

  var p1 = new Promise(function (resolve, reject) {
    resolve(123);
  }).then((res) => {
    console.log(res);
  });
  console.log(p1, 222);
复制代码

image.png 我们可以发现,.then执行后,默认返回了Promise的实例。

因此我们可以大胆推测,.then执行的时候实际上是返回了MyPromise的实例,毕竟.then方法也在MyPromise的原型上,所以实现了链式调用。

.then的回调函数执行时,会return一个值,会成为下一个.then的回调函数的实参。这个值可能是一个MyPromise,也可能是常规一个值。如果是一个MyPromise实例,则需要它resolve的值。且,整个.then调用会返回一个MyPromise的新实例。

实现

  function MyPromise(fn) {
    //promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
    this.promiseState = "pending";
    this.promiseValue = undefined;
    var _this = this;
    // 定义了.thenCallback的属性,用来接受.then执行时的回调函数
    this.thenCallback = undefined;

    var resolve = function (value) {
      // 这里判断的是当前的promise的状态
      if (_this.promiseState == "pending") {
        _this.promiseState = "fulfilled";
        _this.promiseValue = value;
        //当传⼊的数据是自定义的Promise
        if (value instanceof MyPromise) {
        // 直接调用.then拿到resolve的值,并把值给thenCallback让新的promise实例拿到
          value.then(function (res) {
            _this.thenCallback(res);
          });
        } else {
          // 如果传入的数据是普通变量
          setTimeout(function () {
            if (_this.thenCallback) {
              _this.thenCallback(value);
            }
          });
        }
      }
    };

    var reject = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "rejected";
        _this.promiseValue = value;
      }
    };
    if (fn) {
      fn(resolve, reject);
    } else {
      throw "Init Error,Please use a function to init MyPromise!";
    }
  }

  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象
    var _this = this;
    // 整个.then执行会返回一个新的MyPromise实例
    return new MyPromise(function (resolve, reject) {
    // 通过下面方式可以拿到上一个promise中resolve的值,因为_this.thenCallback指向上一个promise的thenCallback。
      _this.thenCallback = function (value) {
        var callbackRes = callback(value);
        // 执行resolve,拿到是.then回调函数执行后返回的值,同时会将新的promise状态变成fuifilled,值变成callbackRes
        resolve(callbackRes);
      };
    });
  };

  MyPromise.prototype.catch = function (callback) {};
复制代码

然后我们先执行一下:

  var p = new MyPromise(function (resolve, reject) {
    resolve(123);
  })
    .then(function (res) {
      console.log(res, "===1");
      return 456;
    })
    .then(function (res1) {
      console.log(res1, "===2");
      return 666;
    });
  console.log(p, "===3");

  const p1 = p.then((res) => {
    return new MyPromise(function (resolve, reject) {
      resolve(res + 1);
    });
  });
  console.log(p1, "===4");

  const p2 = p1.then((res) => {
    return "chenjh";
  });
  console.log(p2, "===5");

  var p3 = new MyPromise(function (resolve, reject) {
    resolve(
      new MyPromise(function (resolve, reject) {
        resolve("test");
      })
    );
  });
  console.log(p3, "===6");
复制代码

image.png

代码解析

  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象
    var _this = this;
    return new MyPromise(function (resolve, reject) {
      _this.thenCallback = function (value) {
        var callbackRes = callback(value);
        resolve(callbackRes);
      };
    });
  };
复制代码

我们知道实现链式调用需要返回同一个构造函数new出来的实例,因此我们直接return一个新的MyPromise实例。

我们之前说过要把callback传给调用.then()的那个promise实例,而new出来的promise内部的this指向了新的promise实例,因此我们需要将外层的this给一个变量_this,新的promise实例访问外层的promise实例的thenCallback属性,并将那个属性赋值成一个带形参的函数,在这个函数内部调用callback函数。

而下一个.then方法需要访问当前.then回调函数return的值,因此我们定义了一个callbackRes接受回调函数执行后return的值。并且将这个值传如resolve,可以让下面代码取到。就相当于每次都是new Promise((resolve,reject)=>{resolve(xxxx)}).then()...

    var resolve = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "fulfilled";
        _this.promiseValue = value;
        //当传⼊的数据是自定义的Promise
        if (value instanceof MyPromise) {
          value.then(function (res) {
            _this.promiseValue = res;
            if (_this.thenCallback) {
              _this.thenCallback(res);
            }
          });
        } else {
          // 如果传入的数据是普通变量
          setTimeout(function () {
            if (_this.thenCallback) {
              _this.thenCallback(value);
            }
          });
        }
      }
    };
复制代码

上述代码中,我们需要判断resolve中的参数value值是否是一个MyPromise,如果是的话我们直接调用.then就可以拿到resolve中的参数value值,再去调用_this.thenCallback。

注:上面这一块可能会有点绕,建议多看几遍代码,每一次return一个new promise其实都是一个新的promise实例!!!

实现catch的流程处理

catch的流程类似,参考上面的then的处理即可。

  function MyPromise(fn) {
    //promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
    this.promiseState = "pending";
    this.promiseValue = undefined;
    var _this = this;
    // 定义了.thenCallback的属性,用来接受.then执行时的回调函数
    this.thenCallback = undefined;
    // 定义了.catchCallback的属性.catch执行时的回调函数
    this.catchCallback = undefined;

    var resolve = function (value) {
      if (_this.promiseState == "pending") {
        _this.promiseState = "fulfilled";
        _this.promiseValue = value;
        //当传⼊的数据是自定义的Promise
        if (value instanceof MyPromise) {
          value.then(function (res) {
            _this.promiseValue = res;
            if (_this.thenCallback) {
              _this.thenCallback(res);
            }
          });
        } else {
          // 如果传入的数据是普通变量
          setTimeout(function () {
            if (_this.thenCallback) {
              _this.thenCallback(value);
            }
          });
        }
      }
    };

    var reject = function (err) {
      if (_this.promiseState == "pending") {
        _this.promiseValue = err;
        _this.promiseState = "rejected";
        setTimeout(function () {
          if (_this.catchCallback) {
            _this.catchCallback(err);
          }
        });
      }
    };
    if (fn) {
      fn(resolve, reject);
    } else {
      throw "Init Error,Please use a function to init MyPromise!";
    }
  }

  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象
    var _this = this;
    return new MyPromise(function (resolve, reject) {
      _this.thenCallback = function (value) {
        var callbackRes = callback(value);
        resolve(callbackRes);
      };
    });
  };

  MyPromise.prototype.catch = function (callback) {
    var _this = this;
    return new MyPromise(function (resolve, reject) {
      _this.catchCallback = function (errValue) {
        var callbackRes = callback(errValue);
        resolve(callbackRes);
      };
    });
  };
复制代码

我们尝试调用一下:

  var p = new MyPromise(function (resolve, reject) {
    reject("err");
  });
  p.then(function (res) {
    console.log(res);
    return 111;
  })
    .then(function (res) {
      console.log(res);
      return 111;
    })
    .then(function (res) {
      console.log(res);
      return 111;
    })
    .catch(function (err) {
      console.log(err);
    });
  console.log(p);
复制代码

image.png

  const p = new Promise((resolve, reject) => {
    reject("123");
  });
  console.log(p);
  const p1 = p.then((res) => {
    console.log(res);
    return 1;
  });
  console.log(p1);
  const p2 = p1.catch((err) => {
    console.log(err);
  });
  console.log(p2);
复制代码

image.png 发现虽然得到了3个promise实例,但是是存在问题的。

实现跨对象执行reject

正常的Promise流程中,如果遇到了reject执行,就会去执行.catch的回调。

而按照我们之前的代码流程,Promise对象会⾃动变更状态为rejected并且catch的回调函数⽆法注册,所以Promise的流程就断了。这个时候需要追加判断代码让Promise在rejected时如果没有catchCallback再去检测是否存在thenCallback。

    var reject = function (err) {
      if (_this.promiseState == "pending") {
        _this.promiseValue = 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!";
    }
复制代码

还需要把prototype.then的改成如下:

  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象
    var _this = this;
    return new MyPromise(function (resolve, reject) {
      _this.thenCallback = function (value) {
        //判断如果进⼊该回调时Promise的状态为rejected那么就直接触发后续Promise的catchCallback
        //直到找到catch
        if (_this.promiseState == "rejected") {
          reject(value);
        } else {
          var callbackRes = callback(value);
          resolve(callbackRes);
        }
      };
    });
  };
  // 修改之后执行
    var p = new MyPromise(function (resolve, reject) {
    reject("err");
  });
  p.then(function (res) {
    console.log(res);
    return 111;
  })
    .then(function (res) {
      console.log(res);
      return 111;
    })
    .then(function (res) {
      console.log(res);
      return 111;
    })
    .catch(function (err) {
      console.log(err);
    });
  console.log(p);
复制代码

image.png

实现链式调用的中断

首先给原型增加reject方法

MyPromise.reject = function(value){
    return new MyPromise(function(resolve,reject){
        reject(value)
    })
}
复制代码

再去修改then的方法

  MyPromise.prototype.then = function (callback) {
    //then第⼀次执⾏时注册回调函数到当前的Promise对象
    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);
          }
        }
      };
    });
  };
复制代码
  var p = new MyPromise(function (resolve, reject) {
    resolve(123);
  });
  console.log(p);
  const p1 = p
    .then(function (res) {
      console.log("then1执⾏");
      return 456;
    })
    .then(function (res) {
      console.log("then2执⾏");
      return MyPromise.reject("中断了");
    })
    .then(function (res) {
      console.log("then3执⾏");
      return 789;
    })
    .then(function (res) {
      console.log("then4执⾏");
      return 666;
    })
    .catch(function (err) {
      console.log("catch执⾏");
      console.log(err);
    });
  console.log(p1);
复制代码

image.png

猜你喜欢

转载自juejin.im/post/7110479504564289549