在開發項目中,我們經常需要使用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); } })