前言
最近在使用 Promise 进行开发时,突然对 Promise 的实现机制开始思考,发现自己虽然之前看过 Promise 是如何实现的,但是却不能清晰的梳理并实现出来,于是重新研究并记录。
需求梳理
要实现 Promise,首先需要梳理需求。
-
状态机
- 有三种状态:
pending
、fulfilled
、rejected
- 状态只能从
pending
到fulfilled
或rejected
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
- 接收一个函数执行器,传入
resolve
和reject
并立即执行 then
函数接收onFulfilled?, onRejected?
resolve
函数,携带 valuereject
函数,携带 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
,但是在之前已经实现了 resolvePromise
对 thenable
进行解析,所以只需要构造 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;
}
}