关于Promise一直是前端面试的重难点,对于Promise很多人其实一直是一知半解,毕竟一直用的是axios以及Promise附带的async await而对于最原始的then却接触的比较少,而在面对复杂情景下代码执行顺序时却一头雾水,其实Promise要完全掌握最好的方式就是看会源码会写源码。
接下来我们对Promise有简到繁进行解析与自定义
在面试时我们有时候会被要求进行手写Promise,接到这个题时可能不知道从哪里开始写起。很多人第一时间都想一口气写出来,其实这是陷入了一个误区,在没有需求提示的情况下一口气写代码无异于直接背代码,而Promise几百行的源码不是一下就能全部想起来的,自然一时间就无从下手。
最好的方式就是需求带动代码接下来我们看下嘛几段代码:
代码1:
let promise = new TPromise((resolve, reject) => {
resolve('成功')
})
从上面我们可以看到很明显Promise是一个类,而constructor传入的是一个执行函数,执行函数在constructor执行时又传入了两个函数,面对前一句话新手可能会一头雾水不知道代码改怎么写了,函数该在哪定义,又应该在哪执行成了Promise源码的第一个难题。其实只要记住接下来这句话就很好理解了,“函数在哪里传入就在哪里定义”。很明显在执行函数传入,那么就在执行函数的执行环境进行定义,我们就能得到如下代码:
class TPromise{
constructor(executor){
this.resolve = (value)=>{
}
this.reject = (reason)=>{
}
executor(this.resolve,this.reject)
}
}
不知不觉中其实就已经掌握了高阶函数的精髓。
接下来再对代码进行分析,我们都知道Promise内置三种状态:pending,resolved,rejected。pending可以转换到resolved,rejected但不可逆,且resolved,rejected不可互相转换。
前半句很好理解,只要源码中不要出现resolved->pending这种逻辑的代码就行,而后半句其实意思就是如果已经转换为resolved就拒绝执行reject函数(rejected也是一样)。
那么我们就可以对代码进行优化实现:
class TPromise{
PromiseState = 'pending'
constructor(executor){
this.resolve = (value)=>{
if(this.PromiseState === 'pending'){
this.PromiseState = 'resolved'
}
}
this.reject = (reason)=>{
if(this.PromiseState === 'pending'){
this.PromiseState = 'rejected'
}
}
executor(this.resolve,this.reject)
}
}
我们有注意到需求代码中有传入一个值:“成功”,那么就先将这个值保存下来:
class TPromise{
PromiseState = 'pending'
PromiseResult = null
constructor(executor){
this.resolve = (value)=>{
if(this.PromiseState === 'pending'){
this.PromiseState = 'resolved'
this.PromiseResult = value
}
}
this.reject = (reason)=>{
if(this.PromiseState === 'pending'){
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
}
executor(this.resolve,this.reject)
}
}
到这里其实constructor主体逻辑已经完成的一半,接下来我们看最重要的then函数
还是老样子,先看怎么使用then函数:
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then((value) => {
console.log(value)
})
我们可以看到then函数定义并传入一个执行函数,这个函数接收一个值,这时候我们就可以把上面保存的值传入进来:
class TPromise {
PromiseState = 'pending'
PromiseResult = null
constructor(executor) {
this.resolve = (value) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'resolved'
this.PromiseResult = value
}
}
this.reject = (reason) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
}
executor(this.resolve, this.reject)
}
then(resolvedFn, rejectedFn) {
if (this.PromiseState === 'resolved') {
resolvedFn(this.PromiseResult)
}
if (this.PromiseState === 'rejected') {
rejectedFn(this.PromiseResult)
}
}
}
接下来进行第二步捕获异常:我们知道在Promise中抛错会使rejectedFn执行那么利用try catch捕获异常后传给reject后改变PromiseState就会使rejectedFn执行并拿到错误值
class TPromise {
PromiseState = 'pending'
PromiseResult = null
constructor(executor) {
this.resolve = (value) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'resolved'
this.PromiseResult = value
}
}
this.reject = (reason) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
}
try {
executor(this.resolve, this.reject)
}catch (error) {
this.reject(error)
}
}
then(resolvedFn, rejectedFn) {
if (this.PromiseState === 'resolved') {
resolvedFn(this.PromiseResult)
}
if (this.PromiseState === 'rejected') {
rejectedFn(this.PromiseResult)
}
}
now,我们已经做到将then函数实现高阶函数调用,但是Promise其实是支持异步的,这也就意味着then函数传入的两个执行函数,需要在执行resolve后再执行,这里可以使用消息订阅模式又叫生产者消费者模式,简而言之就是将then传入的执行函数暂时保存在实例的属性上(注意保存的是柯里化后的函数,方便后续调用,用过react的应该知道),接下来再在resolve内部写入执行刚刚保存函数的代码
class TPromise {
PromiseState = 'pending'
PromiseResult = null
resolve_then_callbacks = []
reject_then_callbacks = []
constructor(executor) {
this.resolve = (value) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'resolved'
this.PromiseResult = value
this.resolve_then_callbacks.forEach((callback) => {
callback()
})
}
}
this.reject = (reason) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'rejected'
this.PromiseResult = reason
this.reject_then_callbacks.forEach((callback) => {
callback()
})
}
}
try {
executor(this.resolve, this.reject)
}catch (error) {
this.reject(error)
}
}
then(resolvedFn, rejectedFn) {
if (this.PromiseState === 'resolved') {
resolvedFn(this.PromiseResult)
}
if (this.PromiseState === 'rejected') {
rejectedFn(this.PromiseResult)
}
if (this.PromiseState === 'pending') {
this.resolve_then_callbacks.push(() => {//注意这里不单单传入的是函数变量,而是函数柯里化
resolvedFn(this.PromiseResult)
})
this.reject_then_callbacks.push(() => {//之所以使用数组保存是因为then函数支持多次调用有效
rejectedFn(this.PromiseResult)
})
}
}
}
到这里我们已经完成了then函数的单次调用基本功能,我们看接下来的Promise需求代码:
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then((value) => {
console.log(value)
return ‘ok’
}).then((value) => {
console.log(value)
})
我们可以注意到then函数后面又跟了一个.then,这意味着then函数有返回值而且这个返回值甚至还能调用then函数,那么这个返回值是什么就很清楚了,我们只需要再返回一个Promsie的实例就行了,但返回一个实例还不行,因为我们知道then函数能够执行是因为类Promise的执行器函数内部执行了resolve函数改变了PromiseState,那么意味着我们初始化并返回实例时,需要在初始化时执行resolve或reject函数。
那么问题来了resolve或reject函数是需要传入值的,那我们传入什么值呢,我们依旧从需求入手,发现“正版”Promsie是可以实现值传递的,那我们就传入上一个then函数执行后返回的值
then(resolvedFn, rejectedFn) {
return new IPromise((resolve, reject) => {
if (this.PromiseState === 'resolved') {
let result = resolvedFn(this.PromiseResult)
resolve(result)
}
if (this.PromiseState === 'rejected') {
let result = rejectedFn(this.PromiseResult)
resolve(result)
}
if (this.PromiseState === 'pending') {
this.resolve_then_callbacks.push(() => {
let result = resolvedFn(this.PromiseResult)
resolve(result)
})
this.reject_then_callbacks.push(() => {
let result = rejectedFn(this.PromiseResult)
resolve(result)
})
}
})
}
注:完整代码越来越大,方便起见,其余代码如若未有改动这里就不贴了,最后我会贴上完整代码!
上面的代码只是做到正常返回值的情况,而我们“真正”Promise是可以返回Promsie实例的,大家不要小看这个功能,如果可以返回Promise实例也就意味着在可以在执行器函数再继续进行异步操作,这在很多异步任务是非常方便的,如果你做过经典的面试题红绿灯就会知道这一点有多么重要。
我们回来继续分析,既然可以返回Promise实例,那么这个实例就能执行then函数,那么我们就能拿到传入的值传到下一个then中嘛。
then(resolvedFn, rejectedFn) {
return new IPromise((resolve, reject) => {
if (this.PromiseState === 'resolved') {
try {
let result = resolvedFn(this.PromiseResult)
if (result instanceof IPromise) {
result.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
if (this.PromiseState === 'rejected') {
let result = rejectedFn(this.PromiseResult)
resolve(result)
}
if (this.PromiseState === 'pending') {
this.resolve_then_callbacks.push(() => {
let result = resolvedFn(this.PromiseResult)
resolve(result)
})
this.reject_then_callbacks.push(() => {
let result = rejectedFn(this.PromiseResult)
resolve(result)
})
}
})
}
其他返回函数也是一样,这里碍于篇幅我就不一一写了。最后其实可以把函数提出来,后面的代码会有所体现。
then函数已经完成了九成,还有就是一些“容错”代码;
我们有时候会发现,并不是所有then函数都会传入函数返回点什么东西,但是代码依旧能按我们预想的执行顺序执行(比如不返回执行resolve而不是reject)。既然他不返回我们帮他返回就是了,只是值返回undefined就是了
then(resolvedFn, rejectedFn) {
if (typeof rejectedFn !== 'function') {
//异常穿透
rejectedFn = (reason) => {
throw reason
}
}
if (typeof resolvedFn !== 'function') {
//值传递
resolvedFn = (value) => {
return value
}
}
return new IPromise((resolve, reject) => {
const tryReturn = (fn) => {
try {
let result = fn(this.PromiseResult)
if (result instanceof IPromise) {
result.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
if (this.PromiseState === 'resolved') {
tryReturn(resolvedFn)
}
if (this.PromiseState === 'rejected') {
tryReturn(rejectedFn)
}
if (this.PromiseState === 'pending') {
this.resolve_then_callbacks.push(() => {
tryReturn(resolvedFn)
})
this.reject_then_callbacks.push(() => {
tryReturn(rejectedFn)
})
}
})
}
到这里就已经完成Promise的基础功能代码了
我们看一下劳动成果:
class TPromise {
PromiseState = 'pending'
PromiseResult = null
resolve_then_callbacks = []
reject_then_callbacks = []
constructor(executor) {
this.resolve = (value) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'resolved'
this.PromiseResult = value
this.resolve_then_callbacks.forEach((callback) => {
callback()
})
}
}
this.reject = (reason) => {
if (this.PromiseState === 'pending') {
this.PromiseState = 'rejected'
this.PromiseResult = reason
this.reject_then_callbacks.forEach((callback) => {
callback()
})
}
}
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
then(resolvedFn, rejectedFn) {
return new IPromise((resolve, reject) => {
const tryReturn = (fn) => {
try {
let result = fn(this.PromiseResult)
if (result instanceof IPromise) {
result.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
if (this.PromiseState === 'resolved') {
tryReturn(resolvedFn)
}
if (this.PromiseState === 'rejected') {
tryReturn(rejectedFn)
}
if (this.PromiseState === 'pending') {
this.resolve_then_callbacks.push(() => {
tryReturn(resolvedFn)
})
this.reject_then_callbacks.push(() => {
tryReturn(rejectedFn)
})
}
})
}
}
这里大家可能会发现我的代码只要是函数就都是箭头函数的形式,这是因为我所有的代码都是在类中写的并且我希望代码中的所有this都是指向类实例的,就运用闭包以及箭头函数的特性,使代码不用纠结于this的指向。
还有一个就是catch捕获异常的函数,因为前面已经顺手写了异常穿透的代码,所以现在写起来就很简单
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
最后就是不管是 resolve 还是 reject 都会调用 finally 。那么相当于 fianlly 方法替使用者分别调用了一次 then 的 resolved 和 rejected 状态回调。
finally (fn) {
return this.then(
(value) => {
fn();
return value;
},
(reason) => {
fn();
throw reason;
}
);
};
接下来就要轻松许多了,前人栽树后人乘凉。让我们利用现有代码来完成几个类上面的静态方法:
static resolve(data) {
return new IPromise((resolve, reject) => {
if (data instanceof IPromise) {
data.then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
} else {
resolve(data)
}
})
}
static reject(data) {
return new IPromise((resolve, reject) => {
reject(data)
})
}
static all(data) {
return new IPromise((resolve, reject) => {
let count = 0
let resArr = []
// 遍历
for (let i = 0; i < data.length; i++) {
data[i].then(
(v) => {
count++
resArr[i] = v //注意顺序
if (count === data.length) {
resolve(resArr)
}
},
(r) => {
reject(r)
}
)
}
})
}
static race(data) {
return new IPromise((resolve, reject) => {
for (let i = 0; i < data.length; i++) {
data[i].then(
(v) => {
resolve(v)
},
(r) => {
reject(r)
}
)
}
})
}
static allSettled(promises){
return new IPromise((resolve, reject) => {
if (promises.length === 0) {
resolve([]);
} else {
let result = [];
let index = 0;
for (let i = 0; i < promises.length; i++) {
promises[i]
.then(
(value) => {
result[i] = {
status: 'fulfilled',
value,
};
},
(reason) => {
result[i] = {
status: 'rejected',
reason,
};
}
)
.finally(() => {
if (++index === promises.length) {
return resolve(result);
}
});
}
}
});
}
static any(promises){
return new IPromise((resolve, reject) => {
if (promises.length === 0) {
return resolve();
} else {
let result = [];
let index = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
return resolve(value);//有结果就返回
},
(reason) => {
result[i] = reason;
if (++index === promises.length) {//不断计数,直到都是失败的Promise
return reject(new AggregateError(result));
}
}
);
}
}
});
}
到这里,所有Promise代码其实都已经完成了,但其实一直有一个坑没有填,也是Promsie的灵魂,就是在Promsie中then执行的的函数其实并不是在这一次事件循环中执行,而是在下一次事件循环中的宏任务中执行,所以我们的代码要做一些许修改;只需要在callbacks遍历执行函数这一段的代码放入异步队列,还有一个就是tryreturn代码片段放入异步队列即可