手把手教你写promise源码

346 阅读12分钟

本文将通过一步步详细的分析,带你从0开始,写出promise源码。

promise类的核心逻辑实现

首先,以终为始,我们先来看看promise最终实现的样子

let promise = new Promise((resolve, reject) => {
  resolve('成功');
  reject('失败');
});
promise.then(
  (value) => {
    console.log(value)
  },
  (reason) => {
    console.log(reason)
  }
);

通过,代码我们可以发现,promise使用了new关键字。构造函数里面传入了一个函数,这个函数带有两个参数resolve和reject。

基于以上分析,我们可以写出以下代码

class MyPromise {  //将类命名为MyPromise类
  constructor(executor){  //构造器中传入一个函数,executor,这个函数立即执行
    executor(resolve,reject)
  }
}

同时我们知道,promise中的resolve和reject是用来实现状态状态转换的。resolve将等待状态转换成为成功状态,reject将等待转换为失败状态。也就是说promise中将有一个状态量,通过promise中的类方法,将这个状态量实现从等待->成功/或者失败的转换。于是改进代码:

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { //类的命名规范
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING //状态变量
  
  resolve(){ //成功时候的状态转换
    this.status = FULFILLED
  }
  reject(){ //失败时候的状态转换
    this.status = REJECT
  }
}

同时,我们知道,状态的转换只能由等待转换到成功或者失败。也就是说,初始状态不是等待时,我们是不会执行状态转换的。上面代码优化成为

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { //类的命名规范
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING //状态变量
  
  resolve(){ //成功时候的状态转换
    if(this.status !== PENDING) return //不是等待状态不执行转换
    this.status = FULFILLED
  }
  reject(){ //失败时候的状态转换
    if(this.status !== PENDING) return //不是等待状态不执行转换
    this.status = REJECT
  }
}

在promise的使用中,使用then来处理回调,即promise.then().这里传入了两个函数,分别表示成功的回调和失败的回调。成功的回调函数有形参value,失败的函数有形参reason,完善promise类如下

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { //类的命名规范
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING //状态变量
  
  resolve = ()=>{ //成功时候的状态转换
    if(this.status !== PENDING) return
    this.status = FULFILLED
  }
  reject= ()=>{ //失败时候的状态转换
    if(this.status !== PENDING) return
    this.status = REJECT
  }
  then(successCallback,failCallback){  //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
      if(this.status === FULFILLED){ 
        successCallback(value)        //status为FULFILLED的回调
      }else if(this.status === REJECT){
        failCallback(reason)         // status为REJECT的回调
      } 
  }
}

同时,我们知道,成功回调successCallback的value和failCallback里面的reason就是在执行状态转换的时候传入的参数。我们将value和reason定义为一个变量。

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { //类的命名规范
  constructor(executor){
    executor(this.resolve,this.reject)
  }

  status = PENDING //状态变量
  value = undefined //成功时候的值
  reason = undefined //失败时的原因

  resolve=value=>{ //成功时候的状态转换
    if(this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value //成功时候保存成功量
  }
  reject=reason=>{ //失败时候的状态转换
    if(this.status !== PENDING) return
    this.status = REJECT
    this.reason = reason //失败时后保存失败原因
  }
  then(successCallback,failCallback){  //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
      if(this.status === FULFILLED){ 
        successCallback(this.value)        //status为FULFILLED的回调
      }else if(this.status === REJECT){
        failCallback(this.reason)         // status为REJECT的回调
      } 
  }
}

module.exports = MyPromise;

以上,promise的核心逻辑就实现完了,现在我们来测试一下代码

const MyPromise = require("./myPromise"); //导入类

let promise = new MyPromise((resolve, reject) => {
  resolve("成功"); //实现成功的函数
  // reject("失败");
});

promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

至此,我们整个promise核心逻辑实现完毕。

promise类中加入异步逻辑

上面我们实现的MyPromise类并没有考虑异步代码的实现。我们在测试代码中间加入异步逻辑。

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  // resolve("成功");
  // reject("失败");
  setTimeout(() => { //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
    resolve("成功");
  }, 2000);
});

promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

因为我们在then方法里面没有实现PENDING的逻辑,我们现在实现。由于等待状态时,还没有实现状态转换,我们先将两个回调函数存起来。然后在状态转换的函数resolve和reject里调用,这样就可以保证回调函数被触发。

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  //类的命名规范
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; //状态变量
  value = undefined; //成功时候的值
  reason = undefined; //失败时的原因
  successCallback = undefined; //成功回调函数
  failCallback = undefined; //失败回调函数

  resolve = (value) => {
    //成功时候的状态转换
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    this.successCallback && this.successCallback(value); //先判断成功回调函数存不存在,存在则调用
  };
  reject = (reason) => {
    //失败时候的状态转换
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    this.failCallback && this.failCallback(reason); //先判断失败回调函数存不存在,存在则调用
  };
  then(successCallback, failCallback) {
    //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
    if (this.status === FULFILLED) {
      successCallback(this.value); //status为FULFILLED的回调
    } else if (this.status === REJECT) {
      failCallback(this.reason); // status为REJECT的回调
    } else {
      //还是等待状态,我们先将连个回调函数存起来
      this.successCallback = successCallback;
      this.failCallback = failCallback;
    }
  }
}

module.exports = MyPromise;

再次使用上面的异步测试代码,测试发现完美执行,至此我们就实现了在promise类中加入异步逻辑。

实现then的多次调用

我们知道,promise的then方法是可以多次调用,但是我们上面的代码中的回调函数只能储存一个函数,所以要进行一些改造让其实现then的多次调用

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  //类的命名规范
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; //状态变量
  value = undefined; //成功时候的值
  reason = undefined; //失败时的原因
  successCallback = []; //成功回调函数
  failCallback = []; //失败回调函数

  resolve = (value) => {
    //成功时候的状态转换
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length) //循环判断成功数组里面是否还有回调函数还没有执行,有则出队列并执行函数
      this.successCallback.shift()(this.value);
  };
  reject = (reason) => {
    //失败时候的状态转换
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);//循环判断是失败数组里面是否还有回调函数还没有执行,有则出队列并执行函数
  };
  then(successCallback, failCallback) {
    //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
    if (this.status === FULFILLED) {
      successCallback(this.value); //status为FULFILLED的回调
    } else if (this.status === REJECT) {
      failCallback(this.reason); // status为REJECT的回调
    } else {
      //还是等待状态,我们先将连个回调函数存起来
      this.successCallback.push(successCallback);
      this.failCallback.push(failCallback);
    }
  }
}

module.exports = MyPromise;

至此,我们实现了then的多次调用。

then的链式调用

我们知道promise是可以是西安链式调用的,但是我们上面的代码并没有实现这个功能。而我们修改一下测试代码

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  // resolve("成功");
  // reject("失败");
  setTimeout(() => {
    //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
    resolve("成功");
  }, 2000);
});

promise //promise链式调用
  .then(
    (value) => {
      console.log(value);
    },
    (reason) => {
      console.log(reason);
    }
  )
  .then((value) => {
    console.log(value);
  });

我们观察链式调用发现,要实现链式调用首先要在每一个then中返回一个promise对象因为只有promise对象才能调用then方法,同时我们还要实现链式then之间的传值。接下类,我们就动手是实现一下then的链式调用

我们先写好测试代码

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  resolve("成功");
  // reject("失败");
  // setTimeout(() => {
  //   //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
  //   resolve("hahha1");
  // }, 2000);
});

promise
  .then((value) => {
    // console.log(123);
    console.log(value);
    return 100;
  })
  .then((value) => {
    console.log(value);
  });

然后改造代码如下

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  //类的命名规范
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; //状态变量
  value = undefined; //成功时候的值
  reason = undefined; //失败时的原因
  successCallback = []; //成功回调函数
  failCallback = []; //失败回调函数

  resolve = (value) => {
    //成功时候的状态转换
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) => {
    //失败时候的状态转换
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => { //定义一个新的promise对象
      //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数,因为传给构造函数的函数是理及执行的所以可以将其他语句放进去
      if (this.status === FULFILLED) {
        let x = successCallback(this.value); //status为FULFILLED的回调
        resolve(x); //在这里实现将上一个promise对象的返回值传递給下一个promise
      } else if (this.status === REJECT) {
        failCallback(this.reason); // status为REJECT的回调
      } else {
        //还是等待状态,我们先将连个回调函数存起来
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback);
      }
    });
    return promise2; //返回这个promise对象
  }
}

module.exports = MyPromise;

现在我们就实现了,then方法的链式调用。但是有的时候我们的then除了返回值还会返回promise对象 。也就是说我们现在要判断返回的x是普通值还是promise对象如果是普通值,直接调用resolve,如果是promise对象,查看promise对象返回的结果,根据结果决定调用返回的是resolve还是reject。

我们先写好测试代码

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  resolve("成功");
  // reject("失败");
  // setTimeout(() => {
  //   //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
  //   resolve("hahha1");
  // }, 2000);
});

promise
  .then((value) => {
    // console.log(123);
    console.log(value);
    return other();
  })
  .then((value) => {
    console.log(value);
  });

function other(){
    return new Promise((resolve,reject)=>{
        resolve('成功123')
    })
}

接下来改进我们原来的代码如下

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  //类的命名规范
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; //状态变量
  value = undefined; //成功时候的值
  reason = undefined; //失败时的原因
  successCallback = []; //成功回调函数
  failCallback = []; //失败回调函数

  resolve = (value) => {
    //成功时候的状态转换
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) => {
    //失败时候的状态转换
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => {
      //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
      if (this.status === FULFILLED) {
        let x = successCallback(this.value); //status为FULFILLED的回调
        resolvePromise(x, resolve, reject); //调用resolvePromise函数
      } else if (this.status === REJECT) {
        failCallback(this.reason); // status为REJECT的回调
      } else {
        //还是等待状态,我们先将连个回调函数存起来
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback);
      }
    });
    return promise2;
  }
}

//判断一个值x是不是promise对象,如果是执行promise对象,再调用x的then方法resolve,如果不是执行reject
function resolvePromise(x, resolve, reject) {
  if (x instanceof Promise) {
    //x是promise对象
    x.then(resolve, reject);
  } else {
    //x是正常值
    resolve(x);
  }
}
module.exports = MyPromise;

现在我们就可以实现对返回值为promise对象的处理了。但是这里有一个问题就是假如在then方法中返回了这个promise对象本身,上面的代码就会出现循环调用的情况,这是不允许出现的。在系统的promise中,这种情况会出现报错,但是不会出现错误。

循环调用的情况:

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  resolve("成功");
  // reject("失败");
  // setTimeout(() => {
  //   //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
  //   resolve("hahha1");
  // }, 2000);
});

let p1 = promise.then((value) => {  //这里发生了promise的循环调用
  // console.log(123);
  console.log(value);
  return p1; //返回调用的promise本身
});

p1.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

function other() {
  return new Promise((resolve, reject) => {
    resolve("成功l");
  });
}

解决这种情况其实也不难,我们只需要在resolvePromise中先判断这个x是不是和调用它的promise是相同的。如果是就不往下执行代码。改进之后的代码如下

//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  //类的命名规范
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; //状态变量
  value = undefined; //成功时候的值
  reason = undefined; //失败时的原因
  successCallback = []; //成功回调函数
  failCallback = []; //失败回调函数

  resolve = (value) => {
    //成功时候的状态转换
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) => {
    //失败时候的状态转换
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
    // while (this.failCallback.length) console.log(this.failCallback);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => {
      //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
      if (this.status === FULFILLED) {
        setTimeout(() => {  //这里有一个小细节,使用在resolvePromise的时候使用了实参promise2,但是promise2还没有完成new,所以是不存在的,我们可以使用setTimeout方法是让语句进入事件队列最后再来执行,这是就可以获取到promise2了
          let x = successCallback(this.value); //status为FULFILLED的回调
          resolvePromise(promise2, x, resolve, reject);
        }, 0);
      } else if (this.status === REJECT) {
        failCallback(this.reason); // status为REJECT的回调
      } else {
        //还是等待状态,我们先将连个回调函数存起来
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback);
      }
    });
    return promise2;
  }
}

//判断一个值x是不是promise对象,如果是执行resolve,如果不是执行reject
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject( //如果是循环调用的话,会报一个类型错误
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  if (x instanceof Promise) {
    //x是promise对象
    x.then(resolve, reject);
  } else {
    //x是正常值
    resolve(x);
  }
}
module.exports = MyPromise;

完善代码增加健壮性

执行器错误

我们先改造一下

我的一些感悟就是:

以终为始分析问题,由promise的使用反推出实现。再使用金字塔原理步步细化。

人类从掌握使用工具开始,就在加速的进步着,掌握工具是实现的一个好方法