2022-05-15 从函数调用反过来实现promise A+规范

370 阅读7分钟

promise原理

promise其实是对回调函数做了封装,本质上没有什么新的东西

是构造函数,包含一个执行器executor,在promise内部,有三种状态pending、fulfilled和rejected,一旦内部状态发生变化,就不可更改

构造函数的实例对象方法有:

- Promise.prototype.then
- Promise.prototype.catch
- Promise.prototype.finally

构造函数的静态方法有:

- Promise.resolve
- Promise.reject
- Promise.all
- Promise.race
- Promise.any
- Promise.allSettled

下面就让我们带着问题一步一步的去实现promise源码吧~

实现一个最简单的promise

首先我们要知道promise的用法,这样才能根据参数用法去进行代码编写

const p1 = new MyPromise((resolve, reject) => {
  resolve("p1");
});

p1.then(
  res => {
    console.log(res);
  },
  err => {
    console.log(err);
  }
);

这里MyPromise是一个构造函数,构造函数中的代码是同步执行的,这个构造函数接收两个实参resolve和reject,这两个参数是函数需要在构造函数中内部定义,当调用resolve函数时候,会把当前的promise状态改变成fulfilled,而执行reject函数时则改为rejected,二者相互对立,只执行其中一个,另外一个并不会继续执行,简单的MyPromise第一步代码如下:

const isFunction = value => typeof value === "function";
const isObject = value => typeof value === "object" && value !== null;

const PENDING = "pending",
  FULFILLED = "fulfilled",
  REJECTED = "rejected";
class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;

    const resolve = value => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
      }
    };
    const reject = reason => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

上面为了方面判断函数和对象类型,就直接定义了两个函数,构造函数的中执行器executor执行的错误需要在try...catch里捕获,错误被reject出去

然而resolve后的结果只能被创建的promise实例的then方法接收到,then方法接收两个形参(onFulfilled,onRejected),在这两个回调函数里可以接受到promise的resolve或reject的值,then方法的第一版如下:

then(onFulfilled, onRejected) {
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
    onRejected = isFunction(onRejected)
      ? onRejected
      : reason => {
          throw reason;
        };

    if (this.state === FULFILLED) {
      onFulfilled(this.value);
    }
    if (this.state === REJECTED) {
      onRejected(this.reason);
    }
  }

根据MDN的promise中then方法的描述:

image.png

所以对两个回调函数参数做了处理

这个时候在对上述代码执行,则返回结果:

const p1 = new MyPromise((resolve, reject) => {
  resolve("p1");
});

p1.then(
  res => {
    console.log(res); // 输出p1
  },
  err => {
    console.log(err);
  }
);

如果构造函数里有setTimeout等异步任务呢

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1");
  }, 1000);
});

p1.then(
  res => {
    console.log(res);
  },
  err => {
    console.log(err);
  }
);

如上代码,如果在执行器内有异步任务,则执行p1.then时候的this.state的状态是pending,那此时then里的onFulfilled代码则不会被执行

则此时就需要有两个缓存来暂时的存储这些回调函数,等resolve或reject状态发生变化了再依次执行

构造函数里:

class MyPromise{
    constructor(executor){
        ...
       this.onFulfilledCallbacks = [];
       this.onRejectedCallbacks = [];

       const resolve = value => {
         if (this.state === PENDING) {
           this.state = FULFILLED;
           this.value = value;
           this.onFulfilledCallbacks.forEach(fn => fn());
         }
       };
       const reject = reason => {
         if (this.state === PENDING) {
           this.state = REJECTED;
           this.reason = reason;
           this.onRejectedCallbacks.forEach(fn => fn());
         }
       }; 
       ....
    }
    ...
}

then方法里:

then(onFulfilled, onRejected) {
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
    onRejected = isFunction(onRejected)
      ? onRejected
      : reason => {
          throw reason;
        };

    if (this.state === FULFILLED) {
      onFulfilled(this.value);
    }
    if (this.state === REJECTED) {
      onRejected(this.reason);
    }
    if (this.state === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      });
    }
}

以上代码加完后,上面setTimeout例子就会延迟1秒输出p1

then的返回值是什么,链式调用呢

const p1 = new MyPromise((resolve, reject) => {
  resolve("p1");
});

const p2 = p1.then(
  res => {
    console.log(res); // 输出p1
  },
  err => {
    console.log(err);
  }
);

p2.then(res => { // 报错
  console.log(res);
});

then方法的返回值根据MDN介绍:

image.png

所以它会返回一个新的promise,其中的状态会根据上述情况而定then方法里:

...
const p = new MyPromise((resolve, reject) => {
  let x;
  if (this.state === FULFILLED) {
    x = onFulfilled(this.value);
    resolve(x);
  }
  if (this.state === REJECTED) {
    x = onRejected(this.reason);
    reject(x);
  }
  if (this.state === PENDING) {
    this.onFulfilledCallbacks.push(() => {
      x = onFulfilled(this.value);
      resolve(x);
    });
    this.onRejectedCallbacks.push(() => {
      x = onRejected(this.reason);
      reject(x);
    });
  }
});
return p;
...

创建一个promise实例,再返回,则前面的例子代码会打印出:

image.png

但是then其实是一个微任务,应该在当前同步代码完成之后在执行,给上面例子代码最后加一行

const p1 = new MyPromise((resolve, reject) => {
  resolve("p1");
});

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

p2.then(res => {
  console.log(res);
});

console.log("end");

则输出是p1->p2->end,这其实是不对的,then是微任务,所以这里用queueMicrotask()包裹一层即可:

...
const p = new MyPromise((resolve, reject) => {
  let x;
  if (this.state === FULFILLED) {
    queueMicrotask(() => {
      try {
        x = onFulfilled(this.value);
        resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }
  if (this.state === REJECTED) {
    queueMicrotask(() => {
      try {
        x = onRejected(this.reason);
        resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }
  if (this.state === PENDING) {
    this.onFulfilledCallbacks.push(() => {
      queueMicrotask(() => {
        try {
          x = onFulfilled(this.value);
          resolve(x);
        } catch (err) {
          reject(err);
        }
      });
    });
    this.onRejectedCallbacks.push(() => {
      queueMicrotask(() => {
        try {
          x = onRejected(this.reason);
          resolve(x);
        } catch (err) {
          reject(err);
        }
      });
    });
  }
});
return p;
...

此时上述例子则输出正确:

image.png

如果resolve(new MyPromise())呢

这里相当于resolve个promise了

const p1 = new MyPromise((resolve, reject) => {
  resolve(
    new MyPromise((resolve, reject) => {
      resolve("p1 promise");
    })
  );
});

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

此时输出会是直接一个promise对象信息

image.png

这肯定是不对的,我们期望输出的是p1 promise吗,这里涉及到了状态的递归沿用问题,即resolve里的promise状态作为当前的状态,代码如下:

then(onFulfilled, onRejected) {
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
    onRejected = isFunction(onRejected)
      ? onRejected
      : reason => {
          throw reason;
        };

    const p = new MyPromise((resolve, reject) => {
      let x;
      if (this.state === FULFILLED) {
        queueMicrotask(() => {
          try {
            x = onFulfilled(this.value);
            resolvePromise(p, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.state === REJECTED) {
        queueMicrotask(() => {
          try {
            x = onRejected(this.reason);
            resolvePromise(p, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              x = onFulfilled(this.value);
              resolvePromise(p, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              x = onRejected(this.reason);
              resolvePromise(p, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });
    return p;
  }

其中resolvePromise是判断resolve()里参数还是一个promise的作用,而且不能循环引用,具体代码如下:

function resolvePromise(p, x, resolve, reject) {
  let called = false;
  if (p === x) {
    reject(new TypeError("引用错误"));
  }
  if (isObject(x) || isFunction(x)) {
    try {
      const then = x.then;
      if (isFunction(then)) {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(p, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (err) {
      if (called) return;
      called = true;
      reject(err);
    }
  } else {
    resolve(x);
  }
}

只要一个resolve后,其状态就确定了,不能在使用reject,所以用一个called标记下。

完整的代码如下:

const isFunction = value => typeof value === "function";
const isObject = value => typeof value === "object" && value !== null;

const PENDING = "pending",
  FULFILLED = "fulfilled",
  REJECTED = "rejected";
class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;

    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = value => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };
    const reject = reason => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
    onRejected = isFunction(onRejected)
      ? onRejected
      : reason => {
          throw reason;
        };

    const p = new MyPromise((resolve, reject) => {
      let x;
      if (this.state === FULFILLED) {
        queueMicrotask(() => {
          try {
            x = onFulfilled(this.value);
            resolvePromise(p, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.state === REJECTED) {
        queueMicrotask(() => {
          try {
            x = onRejected(this.reason);
            resolvePromise(p, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              x = onFulfilled(this.value);
              resolvePromise(p, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              x = onRejected(this.reason);
              resolvePromise(p, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });
    return p;
  }
}

function resolvePromise(p, x, resolve, reject) {
  let called = false;
  if (p === x) {
    reject(new TypeError("引用错误"));
  }
  if (isObject(x) || isFunction(x)) {
    try {
      const then = x.then;
      if (isFunction(then)) {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(p, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (err) {
      if (called) return;
      called = true;
      reject(err);
    }
  } else {
    resolve(x);
  }
}

最后用专门的脚本测试上述写的代码是否符合promise A+规范,在代码文件的末尾加上:

MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

module.exports = MyPromise;

然后全局安装promises-aplus-tests工具包,使用promises-aplus-tests index.js命令即可测试:

image.png

本文中写的源码全部通过~

还有一些promise的API如下:

Promise.prototype.catch返回一个Promise.resolve()包裹的一个新的promise

相当于Promise.prototype.then(null, onRejected)

catch(onRejected) {
    return this.then(null, onRejected);
  }

Promise.prototype.finally返回一个新的promise

这个方法的回调函数不管成功还是失败都会执行

finally(onFinally) {
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      error =>
        MyPromise.resolve(onFinally()).then(() => {
          throw error;
        })
    );
  }

Promise.resolve返回一个以给定值解析的promise对象

  • 如果这个参数值是一个promise,则直接返回这个promise
  • 如果是个thenable对象(带有then方法的)返回的promise会“跟随”这个thenable的对象,采用它的最终状态
  • 否则返回的promise将以此值完成
  • 不能是自身,因为是无线循环
MyPromise.resolve = function (value) {
  if (value instanceof MyPromise) {
    return value;
  }
  return new MyPromise((resolve, reject) => {
    if (isObject(value) && isFunction(value.then)) {
      queueMicrotask(() => {
        value.then(resolve, reject);
      });
    } else {
      resolve(value);
    }
  });
};

Promise.reject返回一个带有拒绝理由的promise

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

Promise.all接收一个promise的可迭代对象,返回一个promise实例

根据MDN规则:

image.png

MyPromise.all = function (promises) {
  return new MyPromise((resolve, reject) => {
    if (
      !promises ||
      !promises[Symbol.iterator] ||
      typeof promises[Symbol.iterator] !== "function"
    ) {
      reject(new TypeError("promises不是可迭代对象"));
    } else {
      promises = Array.from(promises);
      const result = [];
      let count = 0;
      if (!promises.length) {
        resolve([]);
      } else {
        for (let i = 0; i < promises.length; i++) {
          MyPromise.resolve(promises[i])
            .then(value => {
              result[i] = value;
              count++;
              if (count === promises.length) {
                resolve(result);
              }
            })
            .catch(err => {
              reject(err);
            });
        }
      }
    }
  });
};

Promise.race接收一个promises可迭代对象,返回一个promise实例

MDN中的规则:

image.png

MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
      if (
        !promises ||
        !promises[Symbol.iterator] ||
        typeof promises[Symbol.iterator] !== "function"
      ) {
        reject(new TypeError("参数不是Iterator类型"));
      } else {
        for (const promise of promises) {
          MyPromise.resolve(promise)
            .then(value => {
              resolve(value);
            })
            .catch(err => {
              reject(err);
            });
        }
      }
    });
    }

最后遗留一个问题,我自己也迷惑,如果有知道解决方法的小伙伴欢迎留言

如果在构造函数中resolve(new Promise())的源码该怎么写:

const p1 = new Promise((resolve, reject) => {
  resolve(
    new Promise((resolve, reject) => {
      resolve("resolve promise");
    })
  );
});

const p2 = p1.then(
  res => {
    console.log(res); // 输出resolve promise
  },
  err => {
    console.log(err);
  }
);

遗留问题找到了解决方法

要实现以上问题,核心的resolve()里面的应该返回这个promise的then函数结果,与之前的resolvePromise中的递归类似但又些许差别,代码如下:

...
const resolve = value => {
  // 判断value是否是thenable对象
  if (isObject(value)) {
    try {
      const then = value.then;
      if (isFunction(then)) {
        return then.call(value, resolve, reject);
      }
    } catch (err) {
      return reject(err);
    }
  }

  if (this.state === PENDING) {
    this.state = FULFILLED;
    this.value = value;
    this.onFulfilledCallbacks.forEach(fn => fn());
  }
};
...
// reject函数保持不变

上一个问题的输出结果:

image.png