简易实现符合Promise/A+规范的Promise

191 阅读6分钟

是什么

image.png
根据规范文档我们可以知道,Promise它是一个函数或者对象,这个对象拥有一个可以接受两个函数作为参数的then方法,方法的第一个参数可以接收一个 value 作为成功的回调,第二个参数可以接受一个 reason 作为失败的回调

Promise的状态

image.png
promise有三种状态:fulfilled、pendding、rejected

  • pedding:可以转换为fulfilled和rejected状态
  • fulfilled:一旦成为这种状态就不能改变为其它状态,同时promise拥有一个value
  • rejected:一旦成为这种状态便不能改变为其它状态,同时promise拥有一个reason

回调中决定promise对象状态的能力


fulfilled:

  • 回调函数返回一个js标准值
  • 回调函数一个fulfilled状态的promise对象

rejected:

  • 回调函数抛出错误
  • 回调函数返回一个rejected状态的promise对象

Pormise的使用

Promise是一个构造函数,接受一个执行器,通过这个执行器来决定返回的对象的状态
image.png

promise对象状态改变的实现

定义状态

const PENDDING = "PEDDING",
  FULFILED = "FULFILED",
  REJECTED = "REJECTED";

构造函数实现

class MyPromise {
  constructor(executor) {
    this.status = PENDDING;
    this.value = undefined;
    this.reason = undefined;


    const resolve = (val) => {
      this.status = FULFILED;
      this.value = val;
    };

    const reject = (reason) => {
      this.status = REJECTED;
      this.reason = reason;
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      this.status = REJECTED;
      this.reason = error.message;
    }
  }

实现结果

  • 返回一个js值

image.png
image.png

  • 使promise成为rejected状态

image.png
image.png

  • 抛出一个错误

image.png
image.png

then方法的实现

then方法的实现我们根据规范文档一步一步实现

  1. then方法接受的回调函数是可选的,同时必须是函数,如果不是函数将被忽略,第一个参数为fulfilled状态的回调,同时将value作为参数,第二个参数为rejected状态的回调,同时将reason作为参数
 then(onResolve, onReject) {
      if (this.status === FULFILED) {
           onResolve(this.value);
      }

      if (this.status === REJECTED) {
            onReject(this.reason);
      }

      if (this.status === PENDDING) {
        // 订阅
          this.onResolveCallbackList.push(() => {
              onResolve(this.value);
            
          });
          this.onRejectCallbackList.push(() => {
               onReject(this.reason);
          });
      }
    };

需要注意的是,在调用promise对象的then方法(比如在执行器中使用了setTimeout)时,可能对象还是pedding状态,这是回调是不能执行的,这里使用发布-订阅模式,当状态改变是调用相应的回调

相应修改我们的resolve和reject方法

  const resolve = (val) => {
      this.status = FULFILED;
      this.value = val;

      // 发布
      this.onResolveCallbackList.forEach((fn) => fn(this.value));
    };

    const reject = (reason) => {
      this.status = REJECTED;
      this.reason = reason;

      // 发布
      this.onRejectCallbackList.forEach((fn) => fn(this.reason));
    };

结果查看

  • 成功态
const promise = new MyPromise((resolve, reject) => {
  resolve("success");
});
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);


image.png

  • 失败态
const promise = new MyPromise((resolve, reject) => {
  reject("fail");
});
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

image.png

  • 抛出错误
const promise = new MyPromise((resolve, reject) => {
  throw new Error("error");
});
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

image.png

  • then方法可以多次调用
const promise = new MyPromise((resolve, reject) => {
  throw new Error("error");
});
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

image.png

  • 延迟改变对象状态
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  }, 2000);
});
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);
promise.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

image.png

  1. then方法返回一个promise对象
  • 如果回调函数返回是一个标准的js值或者一个fulfilled态的promise对象,那么返回的promise对象为fulfilled态
  • 如果回调函数抛出错误或者返回一个rejected态的promise对象,那么返回的promise对象为rejected态

修改我们的then函数

首先确保返回的是一个promise对象,那么在then方法中实例化一个对象,现在在执行器中通过回调函数来决定对象的状态

因为在抛出错误时返回一个rejected态的对象,同时回调函数返回的值类型不确定,所以在调用回调时获取到返回值,定义一个resolvePromise方法来处理返回值,同时捕获可能抛出的错误,捕获到错误直接将对象的状态,代码如下

 then(onResolve, onReject) {
    const promise1 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILED) {
        try {
          let x = onResolve(this.value);
          resolvePromise(promise1, x, resolve, reject);
        } catch (error) {
          reject();
        }
      }

      if (this.status === REJECTED) {
        try {
          let x = onReject(this.reason);
          resolvePromise(promise1, x, resolve, reject);
        } catch (error) {
          reject();
        }
      }

      if (this.status === PENDDING) {
        // 订阅
        this.onResolveCallbackList.push(() => {
          try {
            let x = onResolve(this.value);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        });
        this.onRejectCallbackList.push(() => {
          try {
            let x = onReject(this.reason);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        });
      }

      return promise1;
    });
  }

通过resolvePromise方法来改变对象的状态,传入对象,返回值等参数,该方法存在一个问题,因为在调用then方法时,构造函数里面的执行器是立即执行的,调用resolvePromise方法需要传入promise1这个对象,但是此时构造函数尚未执行完毕,获取不到promise1,应对此问题,我们可以使用setTimeout将resolvePromise留在下一个事件循环中执行,此时可以获取到promise1对象

  then(onResolve, onReject) {
    const promise1 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILED) {
        setTimeout(() => {
          try {
            let x = onResolve(this.value);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onReject(this.reason);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        }, 0);
      }

      if (this.status === PENDDING) {
        // 订阅
        this.onResolveCallbackList.push(() => {
          try {
            let x = onResolve(this.value);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        });
        this.onRejectCallbackList.push(() => {
          try {
            let x = onReject(this.reason);
            resolvePromise(promise1, x, resolve, reject);
          } catch (error) {
            reject();
          }
        });
      }

      return promise1;
    });
  }
  • setTimeout执行我们虽然设置了0ms,从机制上来讲,定义的回调函数执行的最短时间≥4ms
  • 在pedding状态中不需要使用setTimeout,因为此刻函数并没有执行,而是将函数添加到订阅列表中

resolvePromise函数的规范实现

  1. 返回值x不能与promise1引用同一个对象。那么首先判断x和promise1的引用,如果是同一个引用就抛出错误

image.png

const resolvePromise = (promise, x, resolve, reject) => {
  if (promise === x) {
    return reject(new TypeError("promise and x refer to the same object"));
  }
};

实现结果
image.png

  1. 返回值x如果是一个对象或者函数,那么我们可能就需要对这个x做是否为promise对象区别处理,那么我们如何来判断这个对象是不是一个promise对象呢?很简单,我们只需要判断这个对象是不是有一个then方法,如果有,那我们就把这个对象当作是promise对象来处理,调用这个对象的then方法。

image.png
代码如下

const resolvePromise = (promise, x, resolve, reject) => {
  if (promise === x) {
    return reject(new TypeError("promise and x refer to the same object"));
  }

  if ((typeof x === "object" && x !== null) || typeof x !== "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            resolve(y);
          },
          (r) => {
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      reject(error);
    }
  } else {
    reject(x);
  }
};

Tips:当我们读取x对象的then方法时,如果该方法设置了拦截,会直接抛出错误,那我们直接将promise对象置为rejected态返回


image.png
执行结果如下
image.png
现在我们直接是resolve的一个js标准值,如果resolve的是一个promise对象,就达不到预期的效果,那么就需要使用递归了,修改代码

const resolvePromise = (promise, x, resolve, reject) => {
  if (promise === x) {
    return reject(new TypeError("promise and x refer to the same object"));
  }

  if ((typeof x === "object" && x !== null) || typeof x !== "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            resolvePromise(promise, y, resolve, reject);
          },
          (r) => {
            resolvePromise(promise, r, resolve, reject);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      reject(error);
    }
  } else {
    reject(x);
  }
};

测试一下
image.png
结果如下
image.png

总结

以上就是符合大部分Promise/A+规范的promise实现源码,大概就是一下几点

  • 如何改变promise对象的状态,同时保证resolve和rejecte只调用一次--判断状态
  • 如何解决then方法的链式调用--返回promise对象
  • 如何根据返回值x来改变then方法返回的promise对象的状态--resolvePromise方法
  • 如何处理x是一个promise对象--then方法
  • 如何解决promise递归的问题