实现 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;
  }
}
相关推荐
小鱼儿亮亮4 小时前
canvas中常见问题的解决方法及分析,踩坑填坑经历
前端·canvas
一枚前端小能手4 小时前
🔥 老板要的功能Webpack没有?手把手教你写个插件解决
前端·javascript·webpack
至善迎风4 小时前
使用国内镜像源解决 Electron 安装卡在 postinstall 的问题
前端·javascript·electron
mit6.8244 小时前
[Upscayl图像增强] docs | 前端 | Electron工具(web->app)
前端·人工智能·electron·状态模式
闯闯桑4 小时前
toDF(columns: _*) 语法
开发语言·前端·spark·scala·apache
xrkhy4 小时前
ElementUI之Upload 上传的使用
前端·elementui
IT_陈寒5 小时前
Vite5.0性能翻倍秘籍:7个极致优化技巧让你的开发体验飞起来!
前端·人工智能·后端
xw55 小时前
uni-app项目Tabbar实现切换icon动效
前端·uni-app
凉、介5 小时前
U-Boot 多 CPU 执行状态引导
java·服务器·前端