带你了解ES6的【Promise】及如何解决回调地狱 和 【async】【await】的使用


一、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、p3resolve 时,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)
})


  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值