手写 Promise
一、前言
Promise 的手写也算是经典的前端题目了,这得要求你对 Promise 非常了解才可以。
Promise 详情的 API 请点这里,这里我简单讲下两个静态方法:
Promise.reject()
:返回一个以给定值解析后的Promise 对象Promise.resolve()
:返回一个带有拒绝原因的Promise对象
还有一件事就是,Promise 是自己带有状态的,共计三种:pending
、fulfilled
、rejected
。
Promise 刚开始创建时,状态为pending
。一旦你的 Promise 执行了 resolve 或者 reject 回调,那么 Promise 后续状态只能为fulfilled
和rejected
的其中一种。
注意 Promise 的原型方法:
Promise.prototype.then()
:有两个参数,第一个处理fulfilled
状态,第二个处理rejected
状态Promise.prototype.catch()
:有一个参数,只处理rejected
状态Promise.prototype.finally()
:有一个参数,无论fulfilled
还是rejected
,都会执行;finally() 经过测试,可以放在 then() 或者 catch() 之前,后面的 then() 或者 catch() 还是会执行。也就是说,finally() 并不会终止后续的处理,并且可以多次设置。
在 Promise 的处理链中(即 then()、catch() 、finally() 组成的链式调用),这有个小例子:
let example=new Promise();
example.then().catch().finally().then().finally();
// 这个就是 Promise 的处理链,pending 状态结束后,处理链会依照顺序依次调用 then() 或者 catch() 或者 finally()
// 举个例子,如果 Promise 状态为 fulfilled,处理到 catch() 时会跳过,进入下一个处理,此时的 catch() 便会失效
// catch() 能传入两个参数,能处理 fulfilled 状态或者 rejected 状态
// finally() 无论 Promise 是那种状态,当处理到这个位置上时都会执行,但是回调函数没有参数
二、代码实现
const PENDING = "pending"; // Promise 执行状态
const FULFUILLED = "fulfuilled"; // Promise 完成状态
const REJECTED = "rejected"; // Promise 失败状态
class MyPromise {
__state = PENDING; // 状态
__callbacks = []; // 回调数组,完成或者失败将要调用的
__value = null; // 正确的值
__reason = null; // 拒绝的值
static resolve(arg) {
return new MyPromise(resolve => {
resolve(arg);
})
}
static reject(arg) {
return new MyPromise((_, reject) => {
reject(arg);
});
}
constructor(callBack) {
try {
callBack((arg) => {
this.__value = arg;
this.__state = FULFUILLED;
this.__main();
}, (arg) => {
this.__reason = arg;
this.__state = REJECTED;
this.__main();
});
} catch (e) {
this.__reason = e;
this.__state = REJECTED;
this.__main();
}
}
__mainIsRunning = false;
__main() {
// 确保 MyPromise 的主体方法只能被执行一次
if(this.__mainIsRunning){
return;
}
this.__mainIsRunning=true;
// 异步处理,同时也是为了 then、catch、finally 能够在此处 __main() 的主体代码执行前,先一步将传入的回调函数组册进入 __callbacks
setTimeout(() => {
for (let callBack of this.__callbacks) {
const func = callBack[this.__state];
func && func();
}
if (this.__state === REJECTED) {
console.error("未捕捉的错误(in MyPromise)", this.__reason);
}
})
}
/**
* 回调函数包装工厂
* 根据 FULFUILLED 或者 REJECTED 状态,使用传入的回调函数进行处理
* @param {Function} func 注册的回调函数
* @param {"fulfuilled"|"rejected"} state 状态
* @returns
*/
__callbackFactory(func, state) {
return () => {
if (typeof func === "function") {
try {
const ans = func(state === FULFUILLED ? this.__value : this.__reason);
// 函数返回结果为 MyPromise 类型, 那么将其状态转移到正在运行的 MyPromise 主体
if (ans instanceof MyPromise) {
this.__state = ans.__state;
if (this.__state === FULFUILLED) {
this.__value = ans.__value;
} else {
// 如果返回的 MyPromise 的状态为 REJECTED,必须重置其为 FULFUILLED,不然会自动在控制台报错
// 原因是 MyPromise.prototype.__main()
ans.__state = FULFUILLED;
this.__reason = ans.__reason;
}
} else {
// 如果函数返回结果不是 MyPromise,那么状态必定为 FULFUILLED
this.__value = ans;
this.__state = FULFUILLED;
}
} catch (e) {
// 捕捉回调函数运行中的错误,此时状态为REJECTED
this.__reason = e;
this.__state = REJECTED;
}
}
}
}
then(fullfilledCallback, rejectedCallback) {
const callback = {
[FULFUILLED]: this.__callbackFactory(fullfilledCallback, FULFUILLED),
[REJECTED]: this.__callbackFactory(rejectedCallback, REJECTED),
}
this.__callbacks.push(callback);
return this;
}
catch (rejectedCallback) {
const callback = {
[FULFUILLED]: null,
[REJECTED]: this.__callbackFactory(rejectedCallback, REJECTED),
}
this.__callbacks.push(callback);
return this;
};
finally(finnallycallBack) {
finnallycallBack = typeof finnallycallBack === "function" ? finnallycallBack : null;
const callback = {
[FULFUILLED]: finnallycallBack,
[REJECTED]: finnallycallBack,
}
this.__callbacks.push(callback);
return this;
}
}
三、效果
// 原生测试
new Promise((resolve, reject) => {
setTimeout(() => reject(1), 1000);
}).then((res) => {
console.log("原生测试 then(1):", res);
return Promise.reject(res + 1);
}, res => {
console.log("原生测试 cath(1):", res);
return Promise.reject(res + 1);
}).catch(res => {
console.log("原生测试 catch(2):", res);
return Promise.resolve(res + 1);
}).finally((res) => {
console.log("原生测试 finally(1):", res)
}).finally((res) => {
console.log("原生测试 finally(2):", res)
}).then(res => {
console.log("原生测试 then(3):", res);
return Promise.reject()
}).finally(() => {
console.log("原生测试 finally(4):原生Promise测试结束!");
})
// 自定义测试
new MyPromise((resolve, reject) => {
setTimeout(() => reject(1), 1000);
}).then((res) => {
console.log("自定义测试 then(1):", res);
return MyPromise.reject(res + 1);
}, res => {
console.log("自定义测试 cath(1):", res);
return MyPromise.reject(res + 1);
}).catch(res => {
console.log("自定义测试 catch(2):", res);
return MyPromise.resolve(res + 1);
}).finally((res) => {
console.log("自定义测试 finally(1):", res)
}).finally((res) => {
console.log("自定义测试 finally(2):", res)
}).then(res => {
console.log("自定义测试 then(3):", res);
return MyPromise.reject()
}).finally(() => {
console.log("自定义测试 finally(4):自定义Promise测试结束!");
})
效果图:
四、总结
手写的 Promise 用的定时器开始运行,当然优先级不如原生 Promise,毕竟 Promise 完成后是 micro task。
Promise 手写不难,还有就是 Promise 其他的静态方法现实其实是最简单的。
注意,Promise.reject() 是异步抛出错误,用 try/catch 是捕捉不到的,因为它只能捕捉同步抛出的错误!
关键点:
- 创建 Promise 的过程中,传入的回调函数是马上执行的,不会在事件循环中执行,且resolve、reject不会终止该回调的执行
- Promise 自身拥有状态;
- Promise 自身拥有对 rejected 状态或者 fulfilled 状态的状态数据保存,我的代码中 rejected 状态是用变量 __reason 保存,fulfilled 状态是用变量 __value 保存的;
- Promise pending 状态结束后,就没有 pending 状态了,只有 fulfilled 或者 rejected 状态 。Promise.reject()、Promise.resolve() 是已经指定了fulfilled 或者 rejected 状态。