在开发项目中,我们经常需要使用ajax发起异步请求获取数据,但是当我们需要从得到的数据中用于请求下一个异步任务时,就会有多个回调函数嵌套在里面,这个时候代码阅读性就会变得很差,维护成本也相对较高,这种回调函数层层嵌套我们称之为回调地狱。
回调地狱:
$.ajax({
url: 'data1.json',
type: 'GET',
success: function (res) {
$.ajax({
url: res.url, // 将 第一个ajax请求成功得到的res 用于第二个ajax请求
type: 'GET',
success: function (res) {
$.ajax({
url: res.url, // 将第二个ajax请求成功得到的res 用于第三个ajax请求
type: 'GET',
success: function (res) {
console.log(res) // {url: "this is data3.json"}
},
error: function(err) {
console.log(err)
}
})
},
error: function(err) {
console.log(err)
}
})
},
error: function(err) {
console.log(err)
}
})
上面出现多个回调函数的嵌套,可读性较差,我们通常使用Promise来优化,使代码从回调地狱中解脱出来
Promise:
什么是promise,在《ES6标准入门》一书中,详细介绍了Promise
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且强大,它最早由社区提出并实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。
Promise对象有以下两个特点:
(1)对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来“承若”;
(2)一旦状态改变就不会再变,任何时候都可以得到这个结果,promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果,这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
关于onFulfilled、onRejected状态,MND是这样解释的
onFulfilled
当Promise变成接受状态(fulfillment)时,该参数作为回调函数被调用(参考: Function)。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果传入的 onFulfilled 参数类型不是函数,则会在内部被替换为(x) => x ,即原样返回 promise 最终结果的函数
onRejected
当Promise变成拒绝状态(rejection )时,该参数作为回调函数被调用(参考: Function)。该函数有一个参数,,即拒绝的原因(the rejection reason)。
上述文档中我们可以知道,当我们new一个Promise对象中,Promise会帮我们执行里面的异步操作,执行后的异步操作会通过Promise中resolve和reject参数传递出去,传递出去的数据我们可以在then中进行操作。
var pro0 = function (state) { return new Promise(function (resolve, reject) { if(state == true) { setTimeout(function () { resolve('第一个异步任务'); }, 1000) }else { setTimeout(function () { reject('失败的异步任务'); }, 1000) } }) }
//当state为true时
pro0(true).then(
(data)=>{console.log(data)},//1s后输出"第一个异步任务"
(err) =>{console.log(err)}) //未输出
//当state为false时
pro0(false).then(
(data)=>{console.log(data)}, //"未输出"
(err) =>{console.log(err)}) //1s后输出"失败的异步任务"
上面的代码我们基本清楚了Promise的原理以及用法,那么Promise是怎么解决回调地狱的呢?
Promise链式调用:
事实上,Promise中的then方法除了可以接收异步请求得到的数据时,还能返回一个新的promise对象,返回来的promise对象可以接着在下一个then中进行操作,这就是所谓的链式调用
var pro0 = function (state) { return new Promise(function (resolve, reject) { if(state == true) { setTimeout(function () { resolve('第一个异步任务'); }, 1000) }else { setTimeout(function () { reject('失败的异步任务'); }, 1000) } }) } var pro1 = function(){ return new Promise(function (resolve, reject) { setTimeout(function () { resolve('第二个异步任务') },1000) }) }
//当state为true时
pro0(true).then(
(data)=>{console.log(data);return pro1()},//1s后输出"第一个异步任务"
(err) =>{console.log(err);return pro1()}//未输出
).then((data)=>{console.log(data)})//2s后输出"第二个异步任务"
//当state为false时
pro0(false).then(
(data)=>{console.log(data);return pro1()},//未输出
(err) =>{console.log(err);return pro1()}//1s后输出"失败的异步任务"
).then((data)=>{console.log(data)})//2s后输出"第二个异步任务"
上面的代码中,我们在pro0的then里返回了一个pro1函数,在下一个then中接收到了pro1函数所传递出来的值
async await
async/await可以看做是promise中then函数的优化,then函数是链式调用,一点接一点,是一种从左到右的横向写法;而async/awiat是从上到下的顺序执行,就像写同步代码一样,更符合编程习惯
var pro0 = function (state) { return new Promise(function (resolve, reject) { if(state == true) { setTimeout(function () { resolve('第一个异步任务'); }, 1000) }else { setTimeout(function () { reject('失败的异步任务'); }, 1000) } }) } var pro1 = function(){ return new Promise(function (resolve, reject) { setTimeout(function () { resolve('第二个异步任务') },1000) }) }
async function getData () {
console.log(await pro0(true))//1s后输出"第一个异步任务"
console.log(await pro1())//2s后输出
}
上面代码中我们为getDate()函数声明了async,这样当我们执行这个getData()函数的时候,里面的promise对象就会一个接一个的执行(await等待);例外,在async/await中,await只能接收resolve对象,如果想要接收reject对象,只能使用传统的try catch去处理。
例外说一下axios,在实例项目开发中,我们经常使用axios配合async/await去发起异步请求处理数据,其实axios可以看作是基于promise的一种请求方式,与ajax相比,它解决了ajax回调地狱的问题,两者的使用方式基本一样。
axios({ url: 'xxx', method: 'get', responseType: 'json', // 默认的
data: { //'a': 1,
//'b': 2,
} }).then(function (response) { console.log(response); console.log(response.data); }).catch(function (error) { console.log(error); }) $.ajax({ url: 'xxx', type: 'get', dataType: 'json', data: { //'a': 1,
//'b': 2,
}, success: function (response) { console.log(response); } })