Promise作为ES6新增的函数,帮助我们解决了回调地狱的难题,让我们的异步代码可以更加清晰简洁,作为一名前端程序员,手写简单版Promise应该是必备的技能。接下来不多说,直接上代码了。
class Commitment {
static PENDING = '待定'; static FULFILLED = '成功'; static REJECTED = '拒绝'; // 定义Promise的状态
constructor(func) {
this.status = Commitment.PENDING; // 初始状态
this.result = null // 调用resolve或者reject时传入的参数结果 这里用null 是因为调用resolve 或者 reject的时候会给结果赋值
this.resolveCallbacks = []
this.rejectCallbacks = [] // 这两个数组用来保存resolve和reject函数 当then里面有异步代码时用来调用
try {
func(this.resolve.bind(this), this.reject.bind(this)); // 需要注意this的指向问题 这里如果不绑定this的话 调用会报错
} catch (error) {
this.reject(error)
}
}
resolve(result) {
setTimeout(() => {
if (this.status === Commitment.PENDING) {
this.status = Commitment.FULFILLED;
this.result = result // 将传入的结果赋值给实例的result
this.resolveCallbacks.forEach(callback => {
callback(result)
})
}
})
}
reject(result) {
setTimeout(() => {
if (this.status === Commitment.PENDING) {
this.status = Commitment.REJECTED;
this.result = result
this.rejectCallbacks.forEach(callback => { // 遍历then中的待执行代码 then中有异步操作时需要用
callback(result)
})
}
})
}
then(onFULFILLEN, onREJECTED) {
return new Commitment((resolve, reject) => {
onFULFILLEN = typeof onFULFILLEN === 'function' ? onFULFILLEN : () => {}; // then 里只能传入函数 如果传入的不是函数就赋值为空函数
onREJECTED = typeof onREJECTED === 'function' ? onREJECTED : () => {};
if (this.status === Commitment.PENDING) {
this.resolveCallbacks.push(onFULFILLEN)
this.rejectCallbacks.push(onREJECTED)
}
if (this.status === Commitment.FULFILLED) {
setTimeout(() => {
onFULFILLEN(this.result)
})
}
if (this.status === Commitment.REJECTED) {
setTimeout(() => {
onREJECTED(this.result)
})
}
})
}
}
代码比较多,我们一步步来看,首先用static定义Promise的三种状态,status就是Promise的初始状态,把它设置成PENDING状态。由于new一个Promise是需要传进来一个函数,这里的func就是传进来的函数了,下面两个数组是用来处理Promise中有异步函数时候的,这里先不讲他们两个。
因为如果在Promise中自己抛出一个错误信息的话,Promise会通过reject函数帮你执行这个错误,而不会让你的控制台报错,所以这里要通过try catch来捕获错误函数,这里如果不用的话控制台就会报错。
另外还要注意一点,func中传入的resolve跟reject函数要用bind来绑定this,不然通过new出来的实例来调用这两个函数的时候this指向会出问题。
resolve和reject由于要在时间循环的末尾执行的,因此要在外层套一层setTimeout来让它们变成宏任务。
if 条件语句就是判断当前状态是否为PENDING状态,因为只有PENDING状态才能向FULFILLED和FULFILLED状态转变,判断完后就是改变当前的状态了,将传入的参数赋值给实例result,这里的resolveCallbacks是用来存放在Promise中处于异步函数中的函数,遍历这些函数并依次执行,这就是为什么上面定义这个值的时候用一个空数组的原因,数组的规则是先进先出。下面的rejectCallbacks也是一样道理。
最后就是比较复杂的then函数了,Promise.then()函数这里是可以传两个值的,本人比较菜,之前也不知道,算是学到了,第一个参数是成功时调用的函数,第二个是失败时调用的函数。
为什么要再返回一个我们自己写的实例Commitment呢?因为这里模仿的是Promise的链式调用,也就是Promise.then().then()这种。
由于then需要传入的是函数,Promise在调用then的时候如果传入的不是函数也不会报错,所以这里要通过typeof对参数进行判断,不是函数则给个空函数,这样就不会报错了。
接下来就是对三种状态进行不同的逻辑处理了。
当我们用promise这样调用时,会先输出 666 而不是 555,这是因为setTimeout本身是一个宏任务,而我们的resolve函数它内部也调用了setTimeout,因此它也是个宏任务,知道事件循环的只是就不难理解为什么会先输出 666 了。
这里模拟的就是当调用 then 时,遇到异步任务,而resolve或者reject函数在异步中调用,这个时候状态是没法改变的,所以就要处理PENDING时候的异步任务中的函数,把他们加到对应的Callbacks中
如果是成功状态,那么用一个setTimeout来回调这个传入的函数并把之前保存的result参数传给它。
失败状态也是一样道理的。
至此一个简单版的Promise函数就实现了,文章写的不太完善,仅供自己复习用。