拒绝回调地狱——Promise
前言
记得以前写jquery的ajax时,获取到信息后,将信息利用起来,然后再次根据这个信息来发送ajax或者做一些异步回调方法,那代码场景简直不可想象,一层有一层的回调,如下代码,就好比一层层if else的面条代码一样,不过好在ES6将promise标准化,这样就能改善这种回调地狱的问题,至少提高了代码的可读性和降低后期维护成本。
1 | $.ajax({ |
什么是Promise?
其实它只是个构造函数,用来传递异步操作信息,链式调用,避免层层嵌套的回调函数,接收两个函数参数,resolve和reject,分别表示异步操作执行成功后的回调和失败的回调,并且一定要注意,Promise在声明的时候就已经执行了。
三种状态:
- Pending 进行中
- Fulfilled(resolve) 已完成
- Reject 已失败
两种过程:
- pending -> resolved 表示函数执行成功
- pending -> rejected 表示函数执行失败
注意: 一旦promise状态发生以上任意两种变化,就不能再改变了,任何时候都只能得到改变后的结果。
Promise新建后立马执行
我们来看这么一段代码:
1 | let promise = new Promise(function(resolve,reject) { |
我们发现,只是定义了这个对象而已,并没有调用任何方法,就立马执行了,但是我想控制他的执行步骤,那我们该怎么解决这个问题呢?
Promise的真确打开方式
比如我想实现三秒钟后输出你好,并且自己掌握这个函数的调用,如下:
1 | function sayHi(ms){ |
我们可以将promise包裹在一个可执行函数当中,只有执行了这个函数,promise中的业务逻辑才能执行,但是这个then又是什么东西,为什么是通过resolve来传递参数,这里我们一一来解答。
为什么要这样设计Promise
回想一下,promise给我们做了哪些事?简单来说,就是它替你执行了异步函数(如定时器、ajax请求),等到这个异步函数执行完成了,promise就会来通知你:”你要的结果来啦”,这个时候,如果是真确的结果,那么就是通过resolve的参数传给你,然后你通过then来获取这个结果,错误的结果则是通过reject传给你。这个时候就在catch中获得错误信息,下面我用一段ajax请求来细说:
1 | function sendAjax(url) { |
理解这段代码就能解决现实中百分之六十的问题,其实如果你用过axios就知道,axios的用法和上面这段ajax的封装的函数类似,几乎是一样的。当然,剩下的百分之四十的问题,我们通过下面的讲解来解决。
Promise.all
假如我们有这么一个需求,一个列表中的信息需要分别调用两个网络数据,只有这两个请求数据都返回了,我们才能将数据渲染到列表上,这里我们要并发发送请求保证性能,但是却又不知道哪个请求是最后到达,假如没有promise,你可能需要用一个计数器,多次判断计数器数量,如下(伪代码):
1 | var allData = []; |
这种方法也即是相当于变相的回调地狱,那么我们用promise的all方法就能轻松解决:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function getData1(){
let promise = new Promise((resolve, reject) => {
$.ajax({
url: "xxx.com/product",
success: function(data) {
resolve(data)
}
});
})
return promise
}
function getData2(){
let promise = new Promise((resolve, reject) => {
$.ajax({
url: "xxx.com/product",
success: function(data) {
resolve(data)
}
});
})
return promise
}
Promise.all([getData1(),getData2()]).then(data => {
console.log(data) // 输出[data1,data2]
}).catch(e=>{console.log(e)})
这里我们将两个ajax各自封装到promise对象中,然后集中在全局对象中的Promise.all汇合执行,这时候,当两个数据都返回(即触发resolve)后,才会输出数据,注意这个数据是一个数组,分前后位置关系。
但是要注意一点,如果all中的其中一个promise返回了reject,那么Promise.all就只会触发catch.
Promise.race
race就表示比赛,顾名思义表示传入多个promise,首先触发resolve的就会触发Promise.race的resolve,这么说有点绕口,我们来个demo:
1 | function setTime1(){ |
这好像看上去,race没什么用,确实,在现实开发中,似乎用到的不多,但是我们可以打开脑洞,做一个网络超市处理:
1 | function getData(){ |
Promise链式调用
我们可以在.then中继续返回一个promise对象,这个对象状态改变后,依然会沿着它之后的then调用下去,如下代码:
1 | var p = new Promise(function(resolve, reject){ |
总结
Promise给我们开发带来了很多便利,尤其是在逻辑较为复杂的时候,多次嵌套回调也变得较为优雅,大大提升了代码的可读性,也算是为维护你代码的小伙伴谋福利啦,文中只是简单的提了平时开发中用的比较多的几个方法,还有一些较为少用的并没有举例出来,具体可以参照阮老师的ES6入门。