前言
笔者最近在整理Promise的内容,今天给大家分享如何从0手搓出一个简单的Promise
Promise结构搭建
第一步实现promise的第一个回调被立马调用
第二步设置TTPromise类中的reslove reject函数,并将其传出
第三步保存reslove reject的参数,方便后续给then过去
第四步设置状态,保证只有在pending状态下才可以调用reslove/reject
// 第四步设置三种状态状态
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
//
class PHPromise {
constructor(excutor) {
this.status = PROMISE_STATUS_PENDING
// 第三步.保存reslove reject 要传递给 res err 的参数
this.value = undefined
this.reason = undefined
// 第二步设置reslove函数
const reslove = (value) => {
// 第四步状态设计,只有pending期间才可以调用
if(this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED
this.value = value
console.log('reslove被调用');
}
}
// 第二步设置reject函数
const reject = (reason) => {
// 第四步状态设计,只有pending期间才可以调用
if(this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
console.log('rejected被调用');
}
}
excutor(reslove,reject)
}
}
const promise = new PHPromise((reslove,reject) => {
reslove()
})
then方法设计
第一步实现在调用reslove/reject的时候,调用then的第一/二个回调函数,并且传递参数<br /> 第二步调整函数onfulfilled,onrejected执行顺序,因为在new Promise的时候就已经调用reslove/reject<br /> 而此时还没有执行then方法,所以onfulfilled,onrejected此时还没被赋值为函数,无法被调用,解决方法<br /> 采用微任务的queueMicrotask调整执行顺序,先让then方法执行。
class PHPromise {
constructor(excutor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
const reslove = (value) => {
if(this.status === PROMISE_STATUS_PENDING) {
// 设计一个微任务调整onfulfilled,onrejected执行
queueMicrotask(() => {
if(this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
// 调用then传入的第一个参数函数
this.onfulfilled(this.value)
})
}
}
const reject = (reason) => {
if(this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if(this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
// 调用then传入的第二个参数函数
this.onrejected(this.reason)
})
}
}
excutor(reslove,reject)
}
// then方法设计
then(onfulfilled,onrejected) {
this.onfulfilled = onfulfilled
this.onrejected = onrejected
}
}
const promise = new PHPromise((reslove,reject) => {
reslove(2222)
}).then(res => {
console.log('res:',res);
},err => {
console.log('err:',err);
})
优化一
解决then方法多次调用问题
思路:将每次then方法传入的函数装入数组中,然后再统一执行
解释:由于上面代码只能在onfulfilled/onrejected添加一个函数,在第二次调用then的时候会直接覆盖上一次的onfulfilled/onrejected函数
解决then方法在状态已经确认时的问题
思路:区分多种状态,设计在fulfilled rejected时直接调用reslove/reject
解释:在比如设置setimeout定时器时,将promise.then放入宏任务中延迟调用,所以在queueMicrotask执行回调函数时,宏任务里面的promise.then方法还未执行,回调函数还没传入onfulfilledFns/onrejectedFns,所以无法执行promise.then的回调函数
class PHPromise {
constructor(excutor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
// 设计
this.onfulfilledFns = []
this.onrejectedFns = []
const reslove = (value) => {
if(this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
// 状态决定
// 为了使只能执行resolve 或者 reject
if(this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
console.log('reslove被调用');
this.onfulfilledFns.forEach(fn => fn(this.value))
})
}
}
const reject = (reason) => {
if(this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if(this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
console.log('rejected被调用');
this.onrejectedFns.forEach(fn => fn(this.reason))
})
}
}
excutor(reslove,reject)
}
then(onfulfilled,onrejected) {
// 多个状态解决方法
// pending 就是在按正常时间添加进fns的时候
if(this.status === PROMISE_STATUS_PENDING) {
this.onfulfilledFns.push(onfulfilled)
this.onrejectedFns.push(onrejected)
}
// fulfilled 到执行遍历调用onfulfilledFns方法时,还没把then的方法添加到onfulfilledFns里面
if(this.status === PROMISE_STATUS_FULFILLED) {
onfulfilled(this.value)
}
// rejected
if(this.status === PROMISE_STATUS_REJECTED) {
onrejected(this.reason)
}
}
}
const promise = new PHPromise((reslove,reject) => {
reslove(2222)
})
// then方法多次调用
// 思路:将then传入的回调函数放入数组中,再遍历调用
promise.then(res => {
console.log('res1:',res);
},err => {
console.log('err1:',err);
})
promise.then(res => {
console.log('res2:',res);
},err => {
console.log('err2:',err);
})
// 在状态确定后then再调用
setTimeout(() => {
promise.then(res => {
console.log('res3:',res);
},err => {
console.log('err3:',err);
})
},1000)
优化二
解决then链式调用
思路:在then方法执行时返回一个promise,同时解决then方法中return和throw Error()的两种情况
return一个普通值的情况
then方法会返回一个promise包裹的值——这里只考虑返回值,不考虑返回promise以及带有then方法的Object,那么如何拿到上一个promise的返回值呢?需要我们分状态拿值
pending状态:因为then方法在上面的reslove以及reject函数里面调用,所以返回值也在上面
那么想要从上面拿的话有点难操作,所以采用函数包裹函数的方法解决
reslove reject状态:返回值直接拿
class PHPromise {
then(onfulfilled,onrejected) {
return new TTPromise((reslove,reject) => {
// pending 就是在按正常时间添加进fns的时候
if(this.status === PROMISE_STATUS_PENDING) {
// pending拿返回值情况
this.onfulfilledFns.push(() => {
const value = onfulfilled(this.value)
reslove(value)
})
this.onrejectedFns.push(() => {
const reason = onrejected(this.reason)
reslove(reason)
})
}
// fulfilled rejected
if(this.status === PROMISE_STATUS_FULFILLED) {
// reslove拿返回值情况
const value = (this.value)
reslove(value)
}
if(this.status === PROMISE_STATUS_REJECTED) {
// reject拿返回值情况
const reason = onrejected(this.reason)
reslove(reason)
}
})
}
}
解决throw问题
使用 try catch捕捉错误
then(onfulfilled,onrejected) {
return new TTPromise((reslove,reject) => {
// pending 就是在按正常时间添加进fns的时候
if(this.status === PROMISE_STATUS_PENDING) {
if(onfulfilled) this.onfulfilledFns.push(() => {
try {
const value = onfulfilled(this.value)
reslove(value)
} catch(err) {
reject(err)
}
})
if(onrejected) this.onrejectedFns.push(() => {
try {
const reason = onrejected(this.reason)
reslove(reason)
} catch(err) {
reject(err)
}
})
}
// fulfilled rejected 到执行遍历调用onfulfilledFns方法时,还没把then的方法添加到onfulfilledFns里面
if(this.status === PROMISE_STATUS_FULFILLED) {
try {
const value = (this.value)
reslove(value)
} catch(err) {
reject(err)
}
}
if(this.status === PROMISE_STATUS_REJECTED) {
try {
const reason = onrejected(this.reason)
reslove(reason)
} catch(err) {
reject(err)
}
}
})
}
由于try catch的代码块多次出现,我们将其抽取出来
function excFnWithResult(reslove,reject,value,fn) {
try {
const values = fn(value)
reslove(values)
} catch(err) {
reject(err)
}
}
then(onfulfilled,onrejected) {
return new PHPromise((reslove,reject) => {
// pending 就是在按正常时间添加进fns的时候
if(this.status === PROMISE_STATUS_PENDING) {
if(onfulfilled) this.onfulfilledFns.push(() => {
excFnWithResult(reslove,reject,this.value,onfulfilled)
})
if(onrejected) this.onrejectedFns.push(() => {
excFnWithResult(reslove,reject,this.reason,onrejected)
})
}
// fulfilled
if(this.status === PROMISE_STATUS_FULFILLED) {
excFnWithResult(reslove,reject,this.value,onfulfilled)
}
// rejected
if(this.status === PROMISE_STATUS_REJECTED) {
excFnWithResult(reslove,reject,this.reason,onfulfilled)
}
})
}
finally方法设计
思路
无论是reslove还是reject,都调用finally传入的回调函数
finally(fn) {
this.then(() => {
fn()
},() => {
fn()
})
}
catch方法设计
思路
catch方法本质上来说就是调用then方法第二个回调函数的语法糖,所以要实现catch,可以从调用then的第二个回调函数来下功夫。
可是直接在catch方法里面调用then方法,相当于是链式调用了then,then方法返回的新的promise,不是最初的promise,所以思路是——把then的第二个回调函数,变成thow 一个err丢出去,因为前面定义了,thow error
出去之后,再去.then就会到第二个回调函数
class PHPromise {
then(onfulfilled,onrejected) {
const defaultOnrejected = (err) => {throw err}
onrejected = onrejected || defaultOnrejected
// 以下代码不做改变,先做省略
}
catch(onrejected) {
this.then(undefined,onrejected)
}
}
const promise = new PHPromise((reslove,reject) => {
reject(4444)
// reslove(2222)
})
promise.then(res => {
console.log('res1:',res);
return 1111
}).catch(err => {
console.log('catch的err:',err);
})
优化
在执行下列代码时,你觉得finally方法内部的回调函数会被调用吗?
const promise = new PHPromise((reslove,reject) => {
reslove(2222)
})
promise.then(res => {
console.log('res1:',res);
return 1111
}).catch(err => {
console.log('catch的err:',err);
}).finally(() => {
console.log('finally~~~~~');
})
答案是 —— 不会的
因为我们在上面是没有处理调用then方法时,如果没有传递第一个参数会怎么样,那么也就不会将finally的回调函数push进onfulfilledFn或者onrejectedFns,最后onfulfilledFn/onrejectedFns遍历执行的时候当然也就不会有finally的回调函数参与了。
解决方法很简单——给then方法设定一个当第一个参数没传时的默认值
then(onfulfilled,onrejected) {
const defaultOnrejected = (err) => {throw err}
onrejected = onrejected || defaultOnrejected
// 防止调用catch时res函数为undefined,所以每当undefined时,就要给一个默认函数,让她能继续返回上面函数的返回值
const defaultOnfulfilled = (value) => { return value }
onfulfilled = onfulfilled || defaultOnfulfilled
}
all allSettle
思路:
确定什么时候调用reslove 什么时候调用 reject
static all(promises) {
// 关键在于什么时候调用reslove 什么时候调用reject
return new PHPromise((reslove,reject) => {
const values = []
promises.forEach((promise) => {
promise.then(res => {
values.push(res)
if(values.length === promises.length) {
reslove(values)
}
},err => {
reject(err)
})
})
})
}
static allSettled(promises) {
const values = []
return new PHPromise((reslove) => {
promises.forEach((promise) => {
promise.then(res => {
values.push({ status: 'fulfilled', res })
if(values.length = promises.length) {
reslove(values)
}
},err => {
values.push({ status: 'rejected', err })
if(values.length = promises.length) {
reslove(values)
}
})
})
})
}
race any
这个比较容易,看看就懂了
static race(promises) {
return new PHPromise((reslove,reject) => {
promises.forEach(promise => {
promise.then(res => {
reslove(res)
},err => {
reject(err)
})
})
})
}
static any(promises) {
const values = []
return new PHPromise((reslove,reject) => {
promises.forEach(promise => {
promise.then(res => {
reslove(res)
},err => {
values.push(err)
if(values.length === promises.length) {
return reject(new AggregateError(errors, 'All promises were rejected'))
}
})
})
})
}
完结撒花,文中有什么错误的欢迎各位大神来纠正一下,大家理性探讨,共同进步