Promise链式调用与async函数解决回调地狱问题

在平时我们使用vue的时候经常会看到在请求接口的时候总是会有async与await:

JS请求接口示例

这是为什么呢?不能直接请求接口吗?

这里其实是为了避免出现回调地狱的问题。要理解清楚这个问题不妨从promise的链式调用开始讲起。

什么是回调地狱?

概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调地狱。

我们知道一个axios函数中包含一些url等请求参数和一个.then的回调函数,由于其异步性,一个接口的请求需要等到回调函数成功调回才算结束。于是要是我们在.then里面在嵌入一个axios函数的话就需要等到上一层请求后再进入这层,再由这层回调到上层。代码量一多,不仅可读性差而且异常无法捕获,耦合性严重,牵一发动全身。比如看下面代码:

1
2
3
4
5
6
7
8
9
10
axios({url:'http://hmajax.itheima.net/api/province'}).then(res => {
const pname = res.data.list[5]
console.log(pname);
axios({url:'http://hmajax.itheima.net/api/city!', params: { pname }}).then(res => {
const cname = res.data.list[0]
console.log(cname);
})
}).catch(error => {
console.log(error);
})

我在第二个url中最后加了一个“!”让其错误,并让catch寻找错误的地方。然而我们发现:

catch无法捕获

其错误直接来源于axios的源码处,而且显示Uncaught (in promise),这就是回调地狱最直接的一个弊端处。

好了这里又涉及到一个词叫promise。

何为Promise?

mdn的解释是:“Promise是一个对象,它代表了一个异步操作的最终完成或者失败。”所以说Promise就是一个函数返回的对象,不然每次做回调操作都要自己手动传一个回调函数进去。

promise工作原理

解释:依靠then()方法回返回一个新生成的Promise对象特性,继续串联下一环任务,直到结束。而then()回调函数中的返回值会影响新生成的Promise对象最终状态和结果。这样通过链式调用,可以有效解决回调函数嵌套问题。

1
2
3
4
5
6
7
8
9
const p = new Promise((reslove, reject) => {
setTimeout(() => { //设置时间模拟ajax请求
reslove('北京市')
}, 2000)
})
const p2 = p.then(res => {
console.log(res); //北京
})
console.log(p2 === p); //false

可以看到我们创建了一个新的promise对象并赋值给p,然后再用p触发回调函数,并生成了一个全新的promise对象并赋值给p2。

在最后一行的log中,使用了三等判断进行比较,我们知道三等判断,判断的是两个变量保存的内存地址,最后其返回了false更可以说明生成的是一个全新的promise对象。

所以知道了promise的工作原理,我们就可以这样写:

1
2
3
4
5
6
7
8
9
10
axios({url:'http://hmajax.itheima.net/api/province'}).then(res => {
const pname = res.data.list[5]
console.log(pname);
return axios({url:'http://hmajax.itheima.net/api/city!', params: { pname }}).then(res => {
const cname = res.data.list[0]
console.log(cname);
})
}).catch(error => {
console.log(error);
})

没错,就是在第二个axios前面多了一个return,就可以捕获到第二层的错误了。

成功捕获error

async函数和await

我们现在知道了promise可以解决回调地狱,但是这还是在回调函数里面不断嵌套,可读性极差,这时async函数(意思为“异步”)的引入就极大的改善了这个问题。

定义:async函数是使用async关机字声明的函数。async函数是AsyncFunction构造函数的实例,并且其中允许使用await关键词。async和await关键词让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。

因此我们只需要这样:

1
2
3
4
5
6
7
8
9
const getData = async () => {
const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province'})
const pname = pObj.data.list[5]
const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname }})
const cname = cObj.data.list[0]
console.log(pname);
console.log(cname);
}
getData() //别忘了调用函数

或者:

1
2
3
4
5
6
7
8
9
10
11
12
13
let pname = []
let cname = []
const getData = async () => {
await axios({ url: 'http://hmajax.itheima.net/api/province'}).then(res => {
pname = res.data.list[5]
})
await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname }}).then(res => {
cname = res.data.list[0]
})
console.log(pname);
console.log(cname);
}
getData()

注意上面第二个代码中pname和cname没有用const定义,是因为当我在外面用

1
2
const pname = []
const cname = []

定义时发现报出错误:Assignment to constant variable(把常量赋值给了变量)

这是因为我们使用 const 定义了变量且存在初始值。 后面又给这个变量赋值,所以报错了。ES6 标准引入了新的关键字 const 来定义常量,const 与 let 都具有块级作用域:使用 const 定义的常量,不能修改它的值,且定义的常量必须赋初值;let 定义的是变量,可以进行变量赋值操作,且不需要赋初值。这个错误就是因为我们修改了常量而引起的错误,虽然某些浏览器不报错,但是无效果!所以解决方法就是前面加:

1
2
let pname = []
let cname = []

成功解决问题。

– 这里debug的时候发现const和let这块还有点含糊,const似乎最好是赋值一个对象或数组,再对对象进行赋值。等我哪天彻底搞明白了后单独出一期讲讲_(:3 ⌒゙)__


Promise链式调用与async函数解决回调地狱问题
https://bayeeaa.github.io/2024/05/01/Promise链式调用与async函数解决回调地狱问题/
Author
Ye
Posted on
May 1, 2024
Licensed under