前言:
Promise是es6的新特性之一,在实际开发中被广泛应用,它也是在面试中经常被问到的问题,比如:利用promise考察事件循环机制、promise的几种状态、怎么使用promise处理异步等等。由promise引出来的问题很多,解决这些问题的最好方法不是case by case,而是深入了解promise,万变不离其踪。彻底搞懂promsie,这些问题都影刃而解。
手写实现Promise
先看一下promise的简单使用示例:
let p=new Promise((resolve,reject)=>{
console.log('初始化');
resolve('success');
})
.then((res,rej)=>{
console.log('then执行',res)
})
输出结果:
- 初始化
- then执行 success
复制代码
从易到难,我们先简单实现,然后一点点补充改善。首先我们实现resolve和reject功能,从上面?栗子我们需要注意一下几点:
- promise有三种状态,分别是PENDING,FULFILLED,REJECTED;
- promise初始化时,会立即执行传入的函数,执行函数中会改变promise的状态—>输出了‘初始化’,并改变了Promise的状态;
- 从PENDING可以到FULFILLED状态或者REJECTED状态,但是状态一旦改变后,就不可以再改变;
- 状态改变后,通过then方法执行相应的回调—>promise执行了resolve函数,状态为FULFILLED,并把值传给了then中的res参数;
(1)基于以上四点信息,我们其实就能够写出第一版代码:
class HD{
//定义三种状态
static PENDING="pending";
static FULFILLED="fulfiled";
static REJECTED="rejected";
//类执行函数,实例化的时候自动执行
constructor (executor){
//定义初始值为undefined,后面用来保存resolve,reject传进的参数
this.value=undefined;
//定义初始状态为pending
this.status=HD.PENDING;
//加try...catch,防止在执行executor时出错,比如console.log(a),a不存在报错,则执行rejected函数
try {
executor(this.resolve,this.rejected);
} catch (error) {
this.rejected(error)
}
}
//定义resolve函数
resolve=(value)=>{
//只有状态为pending,才可以转为其他状态
if(this.status == HD.PENDING){
//改变promsie状态
this.status=HD.FULFILLED;
//this.value记录resolve传入的值,供then函数使用
this.value=value;
}
}
//同上
rejected=(reason)=>{
if(this.status == HD.PENDING){
this.status=HD.REJECTED;
this.value=reason;
}
}
then=(onFulfilled,onRejected)=>{
//根据不同状态,在then中执行不同的函数
if(this.status==HD.FULFILLED){
let result=onFulfilled(this.value);
};
if(this.status==HD.REJECTED){
let result=onRejected(this.value);
};
}
}
let p=new HD((res,rej)=>{
console.log('初始化');
res('success');
})
.then((value)=>{
console.log(value)
})
输出:
- 初始化
- success
复制代码
(2)excutor中异步改变状态
上面的代码隐藏着一个问题,比如 :
let p=new HD((res,rej)=>{
setTimeout(()=>{
console.log('初始化');
res('success');
})
})
.then((value)=>{
console.log(value)
})
复制代码
只会输出‘初始化’,不会输出success。因为setTimeout是异步任务,当执行then时,promise的状态还没有改变,仍是pending状态,因此我们需要对上面代码继续改造:
class HD{
//定义三种状态
static PENDING="pending";
static FULFILLED="fulfiled";
static REJECTED="rejected";
//定义一个空数组,存放then执行的回调函数,防止executor异步改变status值
this.callbacks=[];
//类执行函数,实例化的时候自动执行
constructor (executor){
//定义初始值为undefined,后面用来保存resolve,reject传进的参数
this.value=undefined;
//定义初始状态为pending
this.status=HD.PENDING;
//加try...catch,防止在执行executor时出错,比如console.log(a),a不存在报错,则执行rejected函数
try {
executor(this.resolve,this.rejected);
} catch (error) {
this.rejected(error)
}
}
//定义resolve函数
resolve=(value)=>{
//只有状态为pending,才可以转为其他状态
if(this.status == HD.PENDING){
//改变promsie状态
this.status=HD.FULFILLED;
//this.value记录resolve传入的值,供then函数使用
this.value=value;
//执行callbacks里的fulfilled函数,当resolve是异步改变状态时,确保then回调可以执行
this.callbacks.map((callback)=>{
callback.onFulfilled(this.value)
})
}
}
//同上
rejected=(reason)=>{
if(this.status == HD.PENDING){
this.status=HD.REJECTED;
this.value=reason;
this.callbacks.map((callback)=>{
callback.onRejected(this.value)
})
}
}
then=(onFulfilled,onRejected)=>{
//如果是pending状态,把then回调函数存入到callbacks
if(this.status==HD.PENDING){
this.callbacks.push({
onFulfilled:value=>{onFulfilled(value)},
onRejected:value=>{onRejected(value)}
})
};
//根据不同状态,在then中执行不同的函数
if(this.status==HD.FULFILLED){
let result=onFulfilled(this.value);
};
if(this.status==HD.REJECTED){
let result=onRejected(this.value);
};
}
}
let p=new HD((res,rej)=>{
console.log('初始化');
res('success');
})
.then((value)=>{
console.log(value)
})
输出:
- 初始化
- success
这样就可以解决excutor中异步改变状态时,then回调执行问题
复制代码
(3)then的链式调用
我们使用promise,最想使用的功能应该就是then的链式调用,很明显,then返回的应该也是一个promsie对象,这样才能继续调用then方法,从而实现then的链式调用。我们对上面代码继续进行改造:
class HD{
//定义三种状态
static PENDING="pending";
static FULFILLED="fulfiled";
static REJECTED="rejected";
//类执行函数,实例化的时候自动执行
constructor (executor){
this.callbacks=[];
//定义初始值为undefined,后面用来保存resolve,reject传进的参数
this.value=undefined;
//定义初始状态为pending
this.status=HD.PENDING;
//加try...catch,防止在执行executor时出错,比如console.log(a),a不存在报错,则执行rejected函数
try {
executor(this.resolve,this.rejected);
} catch (error) {
this.rejected(error)
}
}
//定义resolve函数
resolve=(value)=>{
//只有状态为pending,才可以转为其他状态
if(this.status == HD.PENDING){
//改变promsie状态
this.status=HD.FULFILLED;
//this.value记录resolve传入的值,供then函数使用
this.value=value;
this.callbacks.map((callback)=>{
callback.onFulfilled(this.value)
})
}
}
//同上
rejected=(reason)=>{
if(this.status == HD.PENDING){
this.status=HD.REJECTED;
this.value=reason;
this.callbacks.map((callback)=>{
callback.onRejected(this.value)
})
}
}
then=(onFulfilled,onRejected)=>{
var promise=new HD((resolve,reject)=>{
//根据不同状态,在then中执行不同的函数
if(this.status==HD.FULFILLED){
try {
let result=onFulfilled(this.value)
//then返回promise的处理
if(result instanceof HD){
//如果onFulfilled返回的是一个promise对象,要进一步处理,不能直接把promise返回
// result.then(value=>{
// //如果then中返回的result是promise,调then函数,把其中值返回出来,经过resolve(value),使得then返回的值是result的resolve值
// resolve(value)
// },reason=>{
// reject(reason)
// })
//这一句是上一段的简化
result.then(resolve,reject);
}else{
//如果返回的不是promise对象,直接返回
resolve(result)
}
} catch (error) {
reject(error)
}
};
if(this.status==HD.REJECTED){
try {
let result=onRejected(this.value)
if(result instanceof HD){
// resolve,reject是父级的
result.then(resolve,reject);
}else{
resolve(result)
}
} catch (error) {
reject(error)
}
};
if(this.status==HD.PENDING){
this.callbacks.push({
onFulfilled:value=>{
try {
let result=onFulfilled(value)
if(result instanceof HD){
//使用父级的resolve,reject从而改变父级的value和状态
result.then(resolve,reject);
}else{
resolve(result)
}
} catch (error) {
//发生异常说明上个promise对像有错误,那就用下个Promise的reject处理,状态变为rejected
reject(error)
}
},
onRejected:value=>{
try {
let result=onRejected(value)
// resolve(result)
//处理then返回promise
if(result instanceof HD){
result.then(resolve,reject);
}else{
resolve(result)
}
} catch (error) {
// onRejected(error)
reject(error)
}
}
})
}
})
return promise
};
}
let p=new HD((resolve,reject)=>{
setTimeout(()=>{
resolve('success1')
},1000)
})
.then(value=>{
return new HD((resolve,reject)=>{
reject('reject')
})
},reason=>{
return new HD((resolve,reject)=>{
reject('reject2')
})
})
.then(value=>{
console.log('value',value)
},reason=>{
console.log('reason',reason)
})
输出:
- reason reject
复制代码
上面改造主要包括两个方面:
- then方法返回一个promise对象;
- then执行回调后如果返回的仍是一个Promise对象,继续对该promise对象进行then的调用,使得then的返回不能是promsie对象的嵌套;
(4)实现Promise.resolve,Promise.reject
对于Promise.resolve().then()的形式,可以看出实现Promise.resolve返回的也是一个Promise对象,并且Promise的状态应该变为resolve。Promise.reject同理。因此我们可以写出这两个函数,在上面的代码中加入两个静态方法:
static resolve=(value)=>{
return new HD((resolve,rejected)=>{
resolve(value)
})
}
static reject=(value)=>{
return new HD((resolve,reject)=>{
reject(value)
})
}
HD.resolve('success111')
.then((res)=>{
console.log(res)
});
输出:
- success111
复制代码
(5)实现Promise.all()方法
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
添加静态all方法:
static all=(promises)=>{
let value=[];
return new HD((resolve,rejected)=>{
promises.map((promise)=>{
promise.then((res)=>{
value.push(res);
if(value.length==promises.length){
resolve(value)
}
},(rej)=>{
rejected(rej)
})
})
})
}
let p=new HD((resolve,reject)=>{
resolve('4')
});
let m=new HD((resolve,reject)=>{
resolve('5')
});
HD.all([p,m]).then((val)=>{console.log(val)},(res)=>{console.log(res)})
输出:
- ['4','5']
复制代码
all方法实现的思路很简单,就是把输入的promise数组进行循环遍历,分别执行其then函数,如果有一个promise函数执行了rejected函数,那么all返回的promise状态就为rejected,并且返回失败的结果,其他Promise暂停执行。如果所有传入的promise都是resolve状态,那么用value保存每一个promsie的返回值,当所有promsie执行完后,最后返回最终结果。
(6)实现Promsie.race方法
Promsie.race方法要比Promise.all()方法实现起来简单,顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
添加静态race方法:
static race=(promises)=>{
return new HD((resolve,rejected)=>{
promises.map((promise)=>{
promise.then(res=>{
resolve(res)
},rej=>{
rejected(rej)
})
})
})
}
let p=new HD((resolve,reject)=>{
setTimeout(() => {
resolve('1')
},2000);
});
let m=new HD((resolve,reject)=>{
setTimeout(() => {
resolve('2')
}, 1000);
});
HD.race([p,m]).then((val)=>{console.log(val)},(res)=>{console.log(res)})
一秒后输出:2
复制代码
race实现思路很简单,一旦有promise执行then回调,就立马调用resolve或者rejected从而改变HD.race返回的promsie的状态,从而实现‘竞争执行’的效果。
总结
以上代码实现了一个promsie函数,支持异步调用、then的链式回调、resolve、rejected、all、race等功能。通过手写Promise这一个过程,让我们对promise有更加深入的理解。如果代码中有不理解、不足或者不对的地方,欢迎大家在留言区评论,一起加油?。谢谢。