文章目录
一、Promise是什么?
1. 基本结构
所谓promise,简单来讲他就是一个盒子,里边保存这未来才会结束的一个事件(一般是一个异步操作)。
Promise盒子中有三个状态:pending(准备状态)、fulfilled(成功状态)、rejected(失败状态)
Promise中可以有两个方法:resolve() 、 reject(),改变三个状态:
pending->fulfilled => resolve()
pending->rejected => reject()
并且,当改变发生之后,状态就会定型,后续通过方法可以获取对应状态的结果。这样,通过promise对象就可以将异步操作以同步的流程表达出来,避免了层层嵌套导致的回调地狱问题。
const promise = new Promise(function(resolve,reject){
if(/* 异步操作 */){
resolve(value);
}else{
rejest(error)
}
})
new Promise接受一个回调函数,回调函数中有两个参数,分别是对应状态改变的两个方法。例如,ajax的success中的数据就可以resove出来,而如果报错的话,就可以reject出来。
注意事项
new Promise构建的promise对象会立即执行,所以一般会把new Promise封装在一个函数中,保证只会调用时才执行。
function p1(){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve({a:1,b:2})
},1000)
})
}
2. resolve/reject值的获取
new Promise生成promise实例之后,可以通过then方法来指定resolve和reject的
回调函数:
function startSync(){
Let p=new Promise((resolve,reject)=>{
console.log('异步程序');//模拟ajax
if(true){
resolve('成功);
}
else{
resolve('失败');
}
})
return p;
}
startSync().then(
function(res){
console.log(res);//res为resolve返回的值
},
function(err){
console.log(err);//err为reject返回的值
});
3. then 和 catch
then(回调函数) -> 获取promise成功的状态结果
catch(回调函数) -> 捕获promise失败的状态结果
基本使用:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
reject({a:1,b:2})
},1000)
})
p.then((res) => {
console.log("res",res)
}).catch((err) => {
console.log("err",err)
})
then方法中可以继续return一个promise对象,然后返回值就是这个promise对象
例:
function p1(){
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve({a:1,b:2})
},1000)
})
return p;
}
function p2(){
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('hello world')
},1000)
})
return p;
}
p1().then((res) => {
return p2();
}).then((res) => {
console.log(res)
}).catch((err) => {
console.log("err",err)
})
在一个链式操作中,不管哪一个环节出错,catch方法都可以捕获到,所以,只写一次就行了。
二. 用Promise解决回调地狱
1. 回调函数
回调函数在执行逻辑上就是“一件事干完之后,在干另一件事!”,当然,咱们也可以自己封装回调函数。
自定义回调函数的基本逻辑就是把函数作为参数传递:
function dost(fun ){
let time =null;let i =1;
time =setInterval(function(){
i -=0.5;
box.style.opacity =i;
if(i<=0){
clearInterval(time );fun ?fun():';
}
},30);
}
dost(function(){alert('hello world')});
2. 同步 / 异步
同步和异步主要来形容方法的执行方式,咱们平常碰到的大部分方法和自定义函数都是同步的。同步的特点是只有上一个方法执行完毕,才能执行下一个同步方法。而异步任务要等所有同步方法执行完毕之后,再执行异步方法。
常见的异步操作:setIntervalval、setTimeout、ajax
所有的同步任务都在主线程中执行,在主线程之外有一个任务队列,所有的异步任务都会进入任务队列,等待同步任务执行完毕之后执行。
3. 回调地狱
在某些逻辑下,使用回调函数可能造成恐怖的 回调地狱!
Ajax的success就是回调函数,例如,调用www.api.com/userinfo接口获取用户信息, 得到用户信息以后,携带用户信息调用www.api.com/order接口获取用户的订单信息,得到订单信息之后,携带订单信息调用www.api.com/goods接口获取点单的商品信息…如果在多做几层逻辑,代码结构会变的很可怕。
那么,通过promise对象就可以很优雅的解决回调地狱问题!
4. 解决回调地狱
通过promise解决一下回调地狱的问题
function getUserInfo(){
return new Promise((resolve,reject)=>{
//发起ajax请求
//在success中回复resolve/reject数据
if(true){
resolve('用户信息');
}else{
reject('错误信息');
}
})
}
function getOrderInfo(userInfo){
return new Promise((resolve,reject)=>{
//发起ajax请求
//在success中回复resolve/reject数据
if(true){
resolve('订单信息');
}else{
reject('错误信息');
}
})
}
function getGoodsInfo(orderInfo){
return new Promise((resolve,reject)=>{
//发起ajax请求
//在success中回复resolve/reject数据
if(true){
resolve('商品信息');
}else{
reject('错误信息');
}
})
}
/*没有处理reject相应的逻辑*/
getUserInfo().then(function(userinfo){
//处理数据
//返回获取订单信息的promise对象
return getOrderInfo(userInfo);
}).then(function(orderinfo){
//处理数据
//返回获取商品信息的promise对象
return getGoodsInfo(orderinfo);
}).then(function(goodsinfo){
//得到商品信息
console.log(goodsinfo)
})
三. 关于promise错误状态的处理
1. catch方法
当中间的某个操作出现reject的时候,因为未return新的promise对象,所以后续的值会出现undefined。这对于程序员处理后续的逻辑将变得比较不舒服。为了解决此类问题,promise引入了catch方法,专门用来处理reject状态。
startSync1().then(val=>{
console.log(val);
return startSync2();
}).then(val=>{
console.log(val);
return startSync3();
}).then(val=>{
console.log(val);
}).catch(err=>{
console.log(err)
})
catch只需要在最后写一次,无论中途哪里出现reject状态,都会进入catch。
catch方法很强大,不仅可以处理reject状态,当then中的程序出现致命错误(程序报错)的时候,也会进入catch中。
1> 当没有catch时:
startSync1().then(val=>{console.log(val);
//startSync()方法不存在
return startSync();
}).then(val=>{
console.log(val);
return startSync3();
}).then(val=>{
console.log(val);
})
结果:
2> 当有catch时:
startSync1().then(val=>{
console.log(val);
//startSync()方法不存在
return startSync();
}).then(val=>{
console.log(val);
return startSync3();
}).then(val=>{
console.log(val);
}).catch(err=>{
console.log(err)
})
结果:
2. 总会执行的方法finally
finally()方法用于指定不管Promise对象最后的状态如何,都会执行的操作。
finally()方法不接收任何参数,并且与Promise对象的状态无关,所以finally方法中执行的逻辑应该是跟promise对象逻辑无关的一个逻辑操作。
四. Promise对象的方法
1. Promise.all() 并发
Promise.all() 方法接收多个promise实例作为参数,这样就可以把异步请求封装到promise实例中,然后同时发起请求。
先来看一下基本使用:
p1、p2、p3 是三个promise实例,包裹在一个数组中,传递给 Promise.all() 方法。
P的状态是由 p1、p2、p3共同决定的,有两种情况:
1> 当 p1、p2、p3 都 resolve 时,p的状态为成功状态,会进入then() 方法。
2> 当 p1、p2、p3 只要出现一次 reject 时,p的状态为失败状态,会进入 catch() 方法。
Let p1 =new Promise((resolve,reject)=>{
if(true){
resolve('成功p1');
}else{
reject('失败p1');
}
});
let p2 =new Promise((resolve,reject)=>{
if(true){
resolve('成功p2');
}else{
reject('失败p2');
}
});
let p3 =new Promise((resolve,reject)=>{
if(true){
resolve('成功p3');
}else{
reject('失败p3');
}
});
1> 当p1、p2、p3都为成功状态时:
let p=Promise.all([p1,p2,p3]);
p.then(val=>{
//['成功p1','成功p2','成功p3']
console.log(val);
}).catch(err=>{
console.log(err);
})
2>只要有一个为失败状态:
Let p=Promise.alL([p1,p2,p3]);
p.then(val=>{
console.log(val);
}).catch(err=>{
//失败p2
console.log(err);
})
1. 使用场景
使用**Promise.all()**方法就可以完成多个异步请求的同时发送。
推荐使用场景:
例如一些游戏场景,需要同时加载不同的信息(环境信息、周围玩家信息、动植信息…)。
但是不是所有的场景都可以用Promise.all()方法,因为只有他里边的所有请求都成功的时候,才会进入then方法,可能就会导致某些逻辑的延迟执行。
不推荐使用场景:
例如,同时加载商品信息和个人信息,本就不想关的两个内容,但是使用了all方法之后必须等两个信息都拿到,才能做渲染的操作。
2. 特殊情况
例如,同时执行10个异步操作,但是其中某个异步请求可能会失败,比如第10条请求失败,因为Promise.all()特性意味着前面9条请求白执行了(因为只要出现一次reject,最后会进入catch方法,并且只得到reject的结果。)
为了解决上述问题,可以给每个参数promise实例添加一个catch方法,当有错误发生时,错误会进入自身的catch方法,并且自身catch方法return的数据,将作为最终结果进入到all方法的then中。
2. Promise.race()
Promise.race() 方法也是同时执行多个promise实例,但是他的特点是:多个promise实例谁先确定状态,就以谁的状态为准!
function p1(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('成功p1');
},4000)
});
}
function p2(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('成功p2');
},2000)
});
}
function p3(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('成功p3');
},3000)
});
}
结果:
Promise.race([p1(),p2(),p3()]).then(val=>{
console.log(val)//成功p2
}).catch(err=>{
console.log(err)
});
根据Promise.race()的特点,可以用在特定场景,比如将来你需要显示天气信息到你的网站,而获取天气信息的第三方网站很多,你也不确定到底哪个接口的速度快,这种情况就可以使用Promise.race()来解决。
3. Promise.allSettled()
参数Promise实例失败时,本次的所有Promise参数白执行,最终进入catch方法。当时的解决办法是未每一个Promise实例添加catch方法。
而Promise.allSettled() 方法专门用来处理以上情况。不管参数Promise实例的状态如何,最终都进入到成功结果(then)中去。只是结果中通过特定的值方式表明成功或者失败情况。
function p1(){
return new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve('成功1'):reject('失败1');
});
}
function p2(){
return new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve('成功2'):reject('失败2');
});
}
function p3(){
return new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve('成功3'):reject('失败3');
});
}
结果:
Promise.alLSettled([p1(),p2(),p3()]).then(val={
console.log(val)
}).catch(err=>{
console.log(err)
});
五. async、await
在很长一段时间里面,人们不得不依靠回调来处理异步代码。使用回调的结果是,代码变得很纠结,不便于理解与维护,值得庆幸的是Promise带来了.then(),让代码变得井然有序,便于管理。于是我们大量使用,代替了原来的回调方式。但是不存在一种方法可以让当前的执行流程阻塞直到promise完成(看下例)。JS里面,我们无法直接原地等promise完成,唯一可以用于提前计划promise完成后的执行逻辑的方式就是通过then附加回调函数。
现在随着async/await的增加,可以让接口按顺序异步获取数据,用更可读,可维护的方式处理回调。可以这么说,async/await是基于Promise的解决异步问题的最终方案!
1. async
async可以将普通函数转换成Promise实例!
async function funAsync(){
}
Let res =funAsync();
console.log(res )//Promise
async函数的返回值将作为Promise实例的resolve的结果!
async function funAsync(){
return 'hello world';
}
funAsync().then(val=>{
console.log(val)//hello world
})
2. await
await就是等待的意思,只可以用在async函数中(用在普通函数中会报错),他一般用来修饰一个Promise对象,等待promise的状态确定之后,等待状态才会结束! 并且await命令会返回他修饰的promise对象的resolve值
“console.log(‘world’)” 程序执行要等到 await 修饰的 promise 执行完毕之后才会执行,这样,使用await就实现了程序阻塞的作用,把一个异步的请求,变相变为了同步任务。
现在通过async、await如何解决“商品信息”问题:
3. 关于async函数的错误状态处理
在async函数中,如果函数体中出现程序错误(变量未定义、方法名字写错…)或者await 后边的promise对象 返回reject状态,都会终止async函数的运行,进入async函数的catch方法中(相当于promise状态变为reject失败状态)。
async function test(){
console.log(aaa)//aaa不存在
}
test().then(val=>{
console.log("成功:"+val);
}).catch(err=>{
console.log("失败:"+err);
})
结果:
async function test(){
let res =await new Promise((resolve,reject)=>{
reject('请求失败');
})
console.log('hello world');//不会执行
}
test().then(val=>{
console.log(val);
}).catch(err=>{
console.log(err);
});
结果:
程序错误咱们不应该考虑,因为咱们在完成项目过程中是不允许出现程序错误的,所以,这种情况不应该发生。
咱们更应该考虑的是如何处理await后promise的reject状态!
单独处理每个业务逻辑的错误状态
async function funSync(){
let r1 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r1大于0.5"):reject("r1错误");
}).catch(err=>{
console.log('r1错误,自行消化错误,不影响其他程序');
})
let r2 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r2大于0.5"):reject("r2错误");
}).catch(err=>{
console.log('r2错误,自行消化错误,不影响其他程序');
})
let r3 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r3大于0.5"):reject("r3错误");
}).catch(err{
console.log('r3错误,自行消化错误,不影响其他程序');
})
//其他程序
let other ='hello world';
return other;
}
funSync().then(val=>{
console.log(val);
}).catch(err=>{
console.log(err)
})
通过try catch处理错误
try、catch是一个错误处理方法,try方法中为要执行的逻辑, 如果程序出现错误,将会进入catch方法中。
try{
//逻辑
console.log(a);//a未定义
}catch(err){
//err为错误信息
console.log(err);
}
统一处理所有await的promise错误逻辑
async function funSync(){
try{//如果要保证几个逻辑都必须正确,可以同时放在一个try中
let r1 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r1大于0.5"):reject("r1错误");
})
Let r2 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r2大于0.5"):reject("r2错误");
})
let r3 =await new Promise((resolve,reject)=>{
Math.random()>0.5 ?resolve("r3大于0.5"):reject("r3错误");})
}catch(err){
console.log(err);//处理错误的逻辑
}
//其他程序
let other ='hello world';
return other;
}
funSync().then(val=>{
console.log(val);
}).catch(err=>{
console.log(err)
})