谈及回调地狱发生得情况和解决办法,就必须追溯到原生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) {
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:3000/'+path)
xhr.send(null)
var ret = null
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
ret = xhr.responseText
}
}
return ret
} let res = queryData('data1')
console.log(res)  // 结果为:undefined

  要想执行异步操作代码的返回内容,就需要使用回调函数,下面介绍一种正确的封装方法:

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')
})

  虽然使用了promise对象,但是一路通过 . 调用方法进行下去,代码的可读性较差。

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
})

Ajax请求回调地狱及解决方案(promise、async和await)的更多相关文章

  1. jQuery笔记之工具方法—Ajax 优化回调地狱

    在上一篇文我们说到了回调地狱不好的地方,今天我们看看怎么来优化它,让它可以运用到实际开发中. 什么是回调地狱?回调地狱就是一个函数里面嵌套了所有功能函数,然后缩略图形成一个三角形. 这样的代码可复用性 ...

  2. Ajax请求回调函数没有被调用

    $.ajax({        type:"post",        url:"http://172.16.41.91:8080/FcsServletSSM/users ...

  3. dotnet webservice处理数据量过大,ajax请求返回500错误解决方案

    ajax请求webservice返回json数据,数据规模过大时ajax请求会得到500的响应,webservice+ajax处理大规模的数据需要在web.config中进行如下配置: <sys ...

  4. 回调地狱以及用promise怎么解决回调地狱

    哈哈哈,我又又又回来了,不好意思,最近枸杞喝的比较到位,精力比较旺盛. 现在我们来聊一聊啥是回调地狱,注意是回调地狱啊   不是RB人民最爱拍的那啥地狱啊,来吧,上车吧少年,这是去幼儿园的车 都让开, ...

  5. promise, async和await

    最开始实现异步的方法:回调函数 method1(function(err, result) { if (err) { throw err; } method2(function(err, result ...

  6. ES系列之Promise async 和 await

    概述 promise是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大. 所谓的promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果). Pro ...

  7. [Winform]线程间操作无效,从不是创建控件的线程访问它的几个解决方案,async和await?

    目录 概述 取消跨线程检查 使用委托异步调用 sync和await 总结 概述 最近在qq群里有一朋友,问起在winform中怎么通过开启线程的方式去处理耗时的操作,比如,查看某个目录下所有的文件,或 ...

  8. javascript回调地狱真的只能Promise来解决吗?js回调地狱,Promise。

    javascript的灵活在于函数可以当作函数的参数来传递,以及它的异步回调思想.但是这就带了一个很严重的问题,那就是回调次数过多,会影响代码结构,多层嵌套影响代码的可阅读性,也不便于书写. 举个例子 ...

  9. $.when()方法监控ajax请求获取到的数据与普通ajax请求回调获取到的数据的不同

    1.$.when(ajax).done(function(data)}); 2.$.ajax().done(function(data){}); 1中的data被封装进一个对象[data, " ...

随机推荐

  1. Java面向对象内存图

    1. java虚拟机的内存划分 2. 苹果手机类 package cn.itcast.day06.demo02; /* 定义一个类,用来模拟“手机”事物. 属性:品牌.价格.颜色 行为:打电话.发短信 ...

  2. C语言:将ss所指字符串中所有下标为奇数位上的字母转换成大写,若不是字母,则不转换。-删除指针p所指字符串中的所有空白字符(包括制表符,回车符,换行符)-在带头结点的单向链表中,查找数据域中值为ch的结点,找到后通过函数值返回该结点在链表中所处的顺序号,

    //将ss所指字符串中所有下标为奇数位上的字母转换成大写,若不是字母,则不转换. #include <stdio.h> #include <string.h> void fun ...

  3. JS中bool值转换与比较

    前言 首先需要知道的是,js中有6个值为false,分别是: 0, '', null, undefined, NaN 和 false, 其他(包括{}, [], Infinity)为true. 可以使 ...

  4. 在Linux系统中使用ntfs、fat32格式的存储设备

    在Linux系统中使用ntfs.fat32格式的存储设备   我们通常使用的移动硬盘或U盘一般都是ntfs或fat32的文件系统,作为一名运维工程师,经常会遇到把移动硬盘或者U盘上的内容拷贝的Linu ...

  5. hive内表和外表的创建、载入数据、区别

    创建表 创建内表 create table customer( customerId int, firstName string, lastName STRING, birstDay timestam ...

  6. RTT之时钟管理

    时钟节拍 :等于 1/T_TICK_PER_SECOND 秒,用 SysTick_Handler实现,在每次加1时都会检查当前线程的时间片是否用完,以及是否有定时器超时.定时值应该为该值的整数倍.非整 ...

  7. 在Windows上使用Docker运行.NET COE应用

    在Windows上使用Docker运行.NET COE应用 执行步骤: 1:安装Docker For Windows(注意:docker for windows-64位Windows 10.必须开启 ...

  8. 使用IDEA导入一个Maven风格的SSM项目

    转自: 方法一: (我用的这种,导入的方法 File->New->Project from existing sources)(同理,important也是一样的) https://how ...

  9. springboot后端时间到前端,相差8小时,时间格式不对

    spring boot后台时间正确,返回给前台的时间不正确,和后台差8个小时 { "code": 1, "msg": "SUCCESS", ...

  10. Python - 模块中的"if __name__ == '__main__':"

    1.1 如果导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码. module1.py: def foo(): print('module 1') f ...