手写promise思路
1. promise本质
本质promise就是一个状态机 + 回调队列 + 链式调用规则
核心就3件事:
- 状态管理
- 回调存储执行
- then 链式调用
2. 第一步: 实现 Promise状态机
promise有三种状态
pending 初始状态
fulfilled 成功
rejected 失败
状态转换规则:
rust
pending -> fulfilled
pending -> rejected
注意:
状态一旦改变就不能再变
所以需要:
js
this.status = "pending"
this.value = undefined
this.reason = undefined
3. 第二步: 实现resolve / reject
Promise 构造函数会接受一个 executor
js
new Promise((resolve,reject)=>{})
这个函数:
- 立即执行
- 会收到
resolve和reject
实现逻辑:
js
// value: resolve的值
// reason: reject的值, 失败原因
const resolve = (value)=>{
if(this.status !== "pending") return
this.status = "fulfilled"
this.value = value
}
const reject = (reason)=>{
if(this.status !== "pending") return
this.status = "rejected"
this.reason = reason
}
注意两点:
- 状态只能改一次
- 保留
value/reason
4. 第三步: 实现then (核心)
Promise必须支持:
js
promise.then(onFulfilled, onRejected)
then 有三个行为:
4.1 情况1: Promise 已经fulfilled
立即执行 onFulfilled
但注意:
必须放到微任务
js
queueMicrotask(()=>{
onFulfilled(this.value)
})
4.2 情况2: Promise 已经rejected
执行onRejected
js
queueMicrotask(()=>{
onRejected(this.reason)
})
4.3 情况3: Promise 还在 pending
这时候问题来了:
resolve 可能未来才执行
所以: 要把回调存起来
js
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
then 里:
js
// 保证回调可以正常使用
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
onFulfilled(this.value)
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
onFulfilled(this.value)
});
});
等到 resolve / rejected时
js
this.onFulfilledCallbacks.forEach(fn=>fn());
或
this.onRejectedCallbacks.forEach(fn=>fn());
5. 第四步: then必须返回新的Promise
规范规定:
javascript
then 一定要返回一个新的 Promise
js
const promise2 = new MyPromise(...)
return promise2
因为Promise需要支持链式调用
js
promise
.then()
.then()
.then()
6. 第五步: then 返回值决定下一个Promise
最难的部分
js
const x = onFulfilled(this.value)
然后:
js
promise2 的状态 = x 决定
规则
6.1 情况1: x是普通值
scss
resolve(x)
例:
js
then(()=>100)
6.2 情况2: x是 promise
js
then(()=>Promise)
那就:
arduino
promise 跟随这个 Promise最后的执行状态
例:
js
then(()=>new Promise(...))
6.3 情况3: x是 thenable
thenable:
js
const obj = { then: function(){} };
// 或
function fn(){
// ....
}
fn.prototype.then = function(){
// ....
}
也要按照 Promise处理
7. 第六步:reslovePromise 算法
所以需要写一个 统一解析函数
js
resolvePromise(promise2,x,resolve,reject)
作用:
解析x的类型
步骤:
7.1 防止循环引用
js
if(promise2 === x){
reject(new TypeError("循环引用"))
}
例:
js
p.then(() => p) // 会死循环
7.2 如果 x 是对象或函数
js
typeof x === 'object' || typeof x === 'function'
说明可能是 thenable。
7.3 取 then
js
then = x.then
7.4 如果then是函数
当做Promise处理:
js
then.call(x, resolve, reject)
使用call的原因是防止里面有this调用
js
const obj = {
value: 111,
then(){
console.log(this.value);
}
}
7.5 如果then 不是函数
说明只是普通对象, 直接resolve:
js
resolve(x)
7.6 called锁
Promise规范规定
resolve / reject 只能调用一次
所以:
js
let called = false;
8. 第七步: 为什么要微任务
Promise 规范规定:
then 回调必须是异步执行的
所以必须:
js
queueMicrotask: 传入一个回调函数, 将回调函数中的代码加入到微任务队列中执行
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/queueMicrotask
而不是同步:
例:
js
Promise.resolve(1)
console.log(2)
// 2
// 1
9. 完整代码:
实现顺序
javascript
1 实现 Promise 状态
2 实现 resolve / reject
3 executor 立即执行
4 then 方法
5 then 返回新 Promise
6 回调队列
7 resolvePromise 解析返回值
8 微任务
完整结构其实只有 三块
js
class MyPromise
constructor
then
resolvePromise
js
class MyPromise {
constructor(executor) {
// 初始状态
this.status = "pending";
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;
// 存储成功和失败的回调函数
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
// 保证状态不可逆
if (this.status !== "pending") return;
this.status = "fulfilled";
this.value = value;
// 执行成功的回调函数
this.onFulfilledCallbacks.forEach((callback) => callback());
};
const reject = (reason) => {
// 保证状态不可逆
if (this.status !== "pending") return;
this.status = "rejected";
this.reason = reason;
// 执行失败的回调函数
this.onRejectedCallbacks.forEach((callback) => callback());
};
// 执行 executor,并捕获异常
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
// .then需要可以返回一个新的promise
// 并且promise的状态是按照回调函数的结果来做的
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === "fulfilled") {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.status === "rejected") {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.status === "pending") {
// 将回调函数保存起来,等到状态改变的时候再执行
onFulfilled &&
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
onRejected &&
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
}
});
return promise2;
}
/**
* 1. 首先需要判断x是否和promise2相等, 如果相等将会造成循环引用, 需要reject出去一个error
* 2. 如果 x 不是对象 或 函数, 则说明是普通值, resolve出去即可
* 3. 如果是对象/函数, 需要看属性/原型上是否有 `then` 函数, 只要有就当成 promise 来处理
* 4. 如果是对象/函数, 但没有`then`函数 或 `then`不是函数, 则直接resolve出去即可
*
* @param {*} promise2 将要返回的promise实例
* @param {*} x 回调函数的返回值
* @param {*} resolve
* @param {*} reject
* @returns
*/
}
function resolvePromise(promise2, x, resolve, reject) {
if (x == promise2) {
reject(new TypeError("Chaining cycle detected for promise"));
return;
}
// 因为null也是object, 所以组合判断下
if ((typeof x === "object" && x !== null) || typeof x == "function") {
// 到这里说明是对象/函数
let then;
// Promise 只能 resolve 或 reject 一次, 做个锁
let called = false;
// 获取 then放到 try...catch中, 防止找不到then属性报错
try {
then = x.then;
// 如果是个函数, 调用它
// called做锁, 避免多次调用resolve 或 reject
// 并且递归调用resolvePromise, 处理then返回的值
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
// reject就不需要再递归调用了
if (called) return;
called = true;
reject(r);
},
);
} else {
// then不是函数就直接 resolve出去
resolve(x);
}
} catch (error) {
// 这边也要判断一下,如果called已经被调用过了, 就不再调用
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}