实现 Promise

前言

最近在使用 Promise 进行开发时,突然对 Promise 的实现机制开始思考,发现自己虽然之前看过 Promise 是如何实现的,但是却不能清晰的梳理并实现出来,于是重新研究并记录。

需求梳理

要实现 Promise,首先需要梳理需求。

  • 状态机

    • 有三种状态: pendingfulfilledrejected
    • 状态只能从 pendingfulfilledrejected
    • fulfilled 携带 value,rejected 携带 reason
  • Promise 构造器

    • 接收一个执行函数,并立即执行。
    • 这个执行函数接收两个参数 (resolve/reject)
    • 执行 resolve 表示状态变成成功
    • 执行 reject 或抛出错误表示状态变成失败
  • 方法

    • then/catch/finally
    • 这些方法注册的回调函数都需要异步执行
  • then 方法

    • 返回一个新的 promise 实例
    • 接收两个参数 then(onFulfilled?, onRejected?)
    • onFulfilled/onRejected 如果非函数,则需要直接值穿透或错误穿透
    • 对于回掉函数处理器返回值 x,需要根据类型分别处理
      • 若 x 为 promise 实例本身,则抛出错误拒绝
      • 若 x 为 thenable 则走 promise 规则解析
      • 若 x 为 基础类型,则 调用 resolve 完成,并将 x 作为 resolve 参数

实现

1. 状态

首先需要先定义三个状态。

ini 复制代码
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

2. 定义 Promise 构造器

然后根据需求,定义一个 Promise 类作为构造器,这个类有以下属性:

  • 状态: status
  • 接收一个函数执行器,传入 resolvereject 并立即执行
  • then 函数接收 onFulfilled?, onRejected?
  • resolve 函数,携带 value
  • reject 函数,携带 reason
js 复制代码
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class MyPromise {
  status = PENDING;
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  resolve(value) {}

  reject(reason) {}

  then(onFulfilled, onRejected) {}
}

then 函数处理

then 函数中,onFulfilled?, onRejected? 是可以不为函数的,当不为函数时,需要做穿透,于是可以想到,需要对这个参数做类型判断,统一参数类型为函数。

js 复制代码
then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    const realOnRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };
  }

同时,then 函数返回一个新的 Promise 实例。

javascript 复制代码
then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    const realOnRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    const promise = new MyPromise((resolve, reject) => { 

    });

    return promise;
  }

在这个新的 Promise 实例中,需要根据状态执行回调函数。对于回掉函数的返回值,需要根据返回值类型进行处理,新建一个 resolvePromise 函数来负责。

根据需求,这里会有三种类型:

  • 基本类型
  • then 函数返回的 promise 实例本身
  • thenable 函数或对象
js 复制代码
const promise = new MyPromise((resolve, reject) => {
  resolve(2);
});
const thenPromise = new Promise((resolve, reject) => {
  resolve(1);
})
// 基本类型
thenPromise.then(
  (value) => {
    return value + 1;
  }
);
// 本身
thenPromise.then(
  (value) => {
    return thenPromise
  }
);
// thenable
thenPromise.then(
  (value) => {
    return promise
  }
);

所以 resolvePromise 函数主要处理以上三种情况:

js 复制代码
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("循环引用"));
  }
  if ((x !== null && typeof x === "object") || typeof x === "function") {
    let then;
    let called = false;
    try {
      then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      }
    } catch (error) {}
  } else {
    resolve(x);
  }
}

这其中对于 thenable 的处理存在递归,以保证所有回调的返回值都得到处理。

有了 resolvePromise 函数,再来完善 then 函数。

js 复制代码
then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    const realOnRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        try {
          const x = realOnFulfilled(this.value);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === REJECTED) {
        try {
          const x = realOnRejected(this.reason);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === PENDING) {
      }
    });
    return promise;
  }

对于状态为已完成或在已失败来说,可以直接处理。但是对于状态处于 pending 的情况,就不能直接执行,需要等待状态改变再去执行对应的回调函数。于是需要增加两个数组,用来缓存回调函数。并且还需要两个参数,用来缓存回调函数返回值。

js 复制代码
class MyPromise {
  status = PENDING;
  value = null;
  reason = null;
  onFulfilledCallbacks = [];
  onRejectedCallbacks = [];
 }

有了缓存回调函数的数组,在 pending 时就只需要将回调函数放入数组缓存即可。

ini 复制代码
then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    const realOnRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        try {
          const x = realOnFulfilled(this.value);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === REJECTED) {
        try {
          const x = realOnRejected(this.reason);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          try {
            const x = realOnFulfilled(this.value);
            resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            const x = realOnRejected(this.reason);
            resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }
    });
    return promise;
  }

resolve/reject

根据需求,resolve/reject 具有很重要的两个功能:

  • 修改状态
  • 触发回调函数
js 复制代码
resolve(value) {
    if (this.status !== PENDING) {
      return;
    }
    this.status = FULFILLED;
    this.value = value;
    this.onFulfilledCallbacks.forEach((fn) => {
      fn();
    });
  }

但是,需求中还要求,所有回调必须异步执行,所以需要定义异步执行器。

javascript 复制代码
const microTaskExecute =
  typeof queueMicrotask === "function"
    ? queueMicrotask
    : (fn) => (typeof process !== "undefined" && process.nextTick ? process.nextTick(fn) : setTimeout(fn, 0));

再完善 resolve

js 复制代码
if (this.status !== PENDING) {
      return;
    }
    this.status = FULFILLED;
    this.value = value;
    microTaskExecute(() => {
      this.onFulfilledCallbacks.forEach((fn) => {
        fn();
      });
    });

但是,以上代码存在一个问题,那就是不能解析 thenable,但是在之前已经实现了 resolvePromisethenable 进行解析,所以只需要构造 resolvePromise 参数,再调用 resolvePromise 即可。

ini 复制代码
resolve(value) {
    if (this.status !== PENDING) {
      return;
    }
    const fulfilled = (v) => {
      this.status = FULFILLED;
      this.value = v;
      microTaskExecute(() => {
        const copy = this.onFulfilledCallbacks.slice(0);
        this.onFulfilledCallbacks.length = 0;
        copy.forEach((fn) => fn(this.value));
      });
    };

    const rejected = (r) => {
      this.status = REJECTED;
      this.reason = r;
      microTaskExecute(() => {
        const copy = this.onRejectedCallbacks.slice(0);
        this.onRejectedCallbacks.length = 0;
        copy.forEach((fn) => fn(this.reason));
      });
    };
    resolvePromise.call({}, value, fulfilled, rejected);
  }

reject 则直接调用即可:

js 复制代码
reject(reason) {
    if (this.status !== PENDING) {
      return;
    }
    this.status = REJECTED;
    this.reason = reason;
    microTaskExecute(() => {
      const copy = this.onRejectedCallbacks.slice(0);
      this.onRejectedCallbacks.length = 0;
      copy.forEach((fn) => fn(this.reason));
    });
  }

完整代码

js 复制代码
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("循环引用"));
  }
  if ((x !== null && typeof x === "object") || typeof x === "function") {
    let then;
    let called = false;
    try {
      then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      }
    } catch (error) {}
  } else {
    resolve(x);
  }
}

const microTaskExecute =
  typeof queueMicrotask === "function"
    ? queueMicrotask
    : (fn) => (typeof process !== "undefined" && process.nextTick ? process.nextTick(fn) : setTimeout(fn, 0));

class MyPromise {
  status = PENDING;
  value = null;
  reason = null;
  onFulfilledCallbacks = [];
  onRejectedCallbacks = [];
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  resolve(value) {
    if (this.status !== PENDING) {
      return;
    }
    const fulfilled = (v) => {
      this.status = FULFILLED;
      this.value = v;
      microTaskExecute(() => {
        const copy = this.onFulfilledCallbacks.slice(0);
        this.onFulfilledCallbacks.length = 0;
        copy.forEach((fn) => fn(this.value));
      });
    };

    const rejected = (r) => {
      this.status = REJECTED;
      this.reason = r;
      microTaskExecute(() => {
        const copy = this.onRejectedCallbacks.slice(0);
        this.onRejectedCallbacks.length = 0;
        copy.forEach((fn) => fn(this.reason));
      });
    };
    resolvePromise.call({}, value, fulfilled, rejected);
  }

  reject(reason) {
    if (this.status !== PENDING) {
      return;
    }
    this.status = REJECTED;
    this.reason = reason;
    microTaskExecute(() => {
      const copy = this.onRejectedCallbacks.slice(0);
      this.onRejectedCallbacks.length = 0;
      copy.forEach((fn) => fn(this.reason));
    });
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    const realOnRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        try {
          const x = realOnFulfilled(this.value);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === REJECTED) {
        try {
          const x = realOnRejected(this.reason);
          resolvePromise(promise, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          try {
            const x = realOnFulfilled(this.value);
            resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            const x = realOnRejected(this.reason);
            resolvePromise(promise, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }
    });
    return promise;
  }
}
相关推荐
妄小闲20 小时前
免费html网页模板 html5网站模板 静态网页模板
前端·html·html5
困惑阿三20 小时前
React 展示Markdown内容
前端·react.js·前端框架
lichong95121 小时前
【大前端++】Android studio Log日志高对比度配色方案
android·java·前端·json·android studio·大前端·大前端++
没头脑的男大21 小时前
如何把pdf转换的excell多个表格合并
java·前端·pdf
妄小闲21 小时前
html网站模板 html网页设计模板源码网站
前端·html
Restart-AHTCM1 天前
前端核心框架vue之(路由篇3/5)
前端·javascript·vue.js
段振轩1 天前
Docker nginx容器部署前端项目。
前端·nginx·docker
让时光到此为止。1 天前
vue的首屏优化是怎么做的
前端·javascript·vue.js
San301 天前
JavaScript 流程控制与数组操作全解析:从条件判断到数据高效处理
javascript·面试·代码规范
温宇飞1 天前
CSS 中如何处理空白字符
前端