Ajax请求回调地狱及解决方案(promise、async和await)
谈及回调地狱发生得情况和解决办法,就必须追溯到原生ajax请求。
// 服务器端接口
app.get(
'/data1'
, (req, res) => {
res.send(
'hi'
)
})
app.get(
'/data2'
, (req, res) => {
res.send(
'hello'
)
})
app.get(
'/data3'
, (req, res) => {
res.send(
'nihao'
)
})
// 启动监听
app.listen(3000, () => {
console.log(
'running...'
)
})
原生ajax请求步骤
var
xhr =
new
XMLHttpRequest();
xhr.open(
'get'
,
'http://localhost:3000/data1'
);
xhr.send(
null
);
xhr.onreadystatechange =
function
() {
if
(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var
ret = xhr.responseText;
console.log(ret)
}
}
又因为发送请求的以上代码需要经过反复复用,我们就需要将它封装为函数,以减少代码的冗余度。下面请看两个封装方法(都是错误的封装):
错误封装1:发生错误的原因是queryData函数本身没有返回值,会默认返回undefined,return ret是写在queryData的内层函数中的。
function
queryData(path) {
var
xhr =
new
XMLHttpRequest();
xhr.open(
'get'
,
'http://localhost:3000/'
+path);
xhr.send(
null
);
xhr.onreadystatechange =
function
() {
if
(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
let
ret = xhr.responseText;
return
ret;
}
}
}
let
res = queryData(‘data1’);
console.log(res);
// 结果为:undefined
这样很容易就让我们想到另一种封装方法——把ret在外层函数中返回。然而这就产生了另一种错误的封装效果。
错误封装2:这种情况下发生错误的原因是ajax请求时异步的,数据ret还没有修改成功时,就已经执行了queryData函数的return代码。这时ret的值并没有被修改,当然还是null。
要想执行异步操作代码的返回内容,就需要使用回调函数,下面介绍一种正确的封装方法:
function
queryData(path, callback) {
var
xhr =
new
XMLHttpRequest();
xhr.open(
'get'
,
'http://localhost:3000/'
+ path);
xhr.send(
null
);
xhr.onreadystatechange =
function
() {
if
(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var
ret = xhr.responseText;
callback(ret);
}
}
}
queryData(
'data1'
,
function
(ret) {
console.log(ret)
// 结果为:hi
})
但是,如果想要按顺序获取接口'data1'、'data2'、'data3'中的数据,就会进行下面的操作,也就造成了回调地狱的问题。
queryData(
'data1'
,
function
(ret) {
console.log(ret)
// 按顺序第一个输出为:hi
queryData(
'data2'
,
function
(ret) {
console.log(ret)
//按顺序第二个输出为:hello
queryData(
'data3'
,
function
(ret) {
console.log(ret)
// 按顺序第三个输出为:nihao
});
});
});
promise方式
为了改造上面的回调地狱问题,诞生了promise。promise其实就是一种语法糖(代码形式发生改变、但是功能不变)。
function
queryData(path) {
return
new
Promise(
function
(resolve, reject) {
// 需要在这里处理异步任务
var
xhr =
new
XMLHttpRequest();
xhr.open(
'get'
,
'http://localhost:3000/'
+ path);
xhr.send(
null
);
xhr.onreadystatechange =
function
() {
// 该函数何时触发?xhr.readyState状态发生变化时
if
(xhr.readyState != 4)
return
;
if
(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var
ret = xhr.responseText;
// 成功的情况
resolve(ret);
}
else
{
// 失败的情况
reject(
'服务器错误'
);
}
}
})
}
queryData(
'data1'
)
.then(ret=>{
console.log(ret)
// 按顺序第一个输出为:hi
// 这里返回的是Promise实例对象,下一个then由该对象调用
return
queryData(
'data2'
);
})
.then(ret=>{
console.log(ret);
// 按顺序第二个输出为:hello
return
queryData(
'data3'
);
})
.then(ret=>{
console.log(ret)
// 按顺序第三个输出为:nihao
})
对于上面代码中使用.then调用的情况,有几点说明:
queryData(
'data1'
)
.then(ret=>{
console.log(ret)
// 顺序输出第一个结果为:hi
// 如果在then方法中没有返回Promise实例对象,那么下一个then由默认产生的Promise实例对象调用
})
.then(ret=>{
console.log(
'-------------------'
+ ret)
// 顺序输出第二个结果为:----------------------undefined
// 如果在then中显式地返回一个具体数据,那么下一个then可以获取该数据
return
456;
})
.then(ret=>{
console.log(
'-------------------'
+ ret)
// 顺序输出第三个结果为:----------------------456
})
上面的代码第二个.then中return的是456,为什么能继续调用.then方法呢?
这是因为return 456 实际上可以理解为下面的三种表示方式:
// Promise.resolve的作用:就是把数据转化为Promise实例对象
// 方式一:
return
Promise.resolve(456);
// 方式二:
return
new
Promise(
function
(resolve, reject) {
resolve(99999);
})
// 方式三:
Promse.resolve =
function
(param) {
return
new
Promise(
function
(resolve, reject) {
resolve(param);
})
}
return
Promise.resolve(88888);
promise对象除了.then方法外还有两个方法可以通过 . 调用,其中.finally是ES7中新增的方法。
.
catch
(ret=>{
// 发生错误时触发
console.log(
'error'
)
})
.finally(ret=>{
// 无论结果成功还是失败都触发:一般用于释放一些资源
console.log(
'finally'
)
})
async和await
下面我们就提出解决回调地狱最好的一种方法,通过使用 async 和 await 。
function
queryData(path) {
return
new
Promise(
function
(resolve, reject) {
// 需要在这里处理异步任务
var
xhr =
new
XMLHttpRequest();
xhr.open(
'get'
,
'http://localhost:3000/'
+ path);
xhr.send(
null
);
xhr.onreadystatechange =
function
() {
// 当readyState值不为0的时候直接返回
if
(xhr.readyState != 4)
return
;
if
(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var
ret = xhr.responseText;
// 成功的情况
resolve(ret);
}
else
{
// 失败的情况
reject(
'服务器错误'
);
}
}
})
}
async
function
getAllData() {
// await执行流程是顺序执行
let
ret1 = await queryData(
'data1'
);
let
ret2 = await queryData(
'data2'
);
let
ret3 = await queryData(
'data3'
);
console.log(ret1)
console.log(ret2)
console.log(ret3)
}
getAllData();
另外,有一点需要提起注意:async函数的返回值是Promise实例对象
async
function
getAllData() {
// await执行流程是顺序执行
let
ret1 = await queryData(
'data1'
);
return
'hello'
;
}
var
ret = getAllData();
console.log(ret)
// 这里输出一个promise对象,并且resolve的数据为hello
ret.then(res=>{
console.log(res)
// 这里输出结果为:hello
})