在es6中,回撥地獄就是多層回撥函數相互巢狀,即回撥函數中巢狀回撥函數的情況;它是為了實現程式碼順序執行而出現的一種操作,它會造成我們的程式碼可讀性非常差,後期不好維護。es6中使用promise來解決回撥地獄的問題。
本教學操作環境:windows7系統、ECMAScript 6版、Dell G3電腦。
在正式瞭解「回撥地獄」之前,我們先了解兩個概念:
1、回撥函數
當一個函數作為引數傳入另一個引數中,並且它不會立即執行,只有當滿足一定條件後該函數才可以執行,這種函數就稱為回撥函數。我們熟悉的定時器和Ajax中就存在有回撥函數:
setTimeout(function(){ //function(){console.log('執行了回撥函數')}就是回撥函數,它只有在3秒後才會執行
console.log('執行了回撥函數');
},3000) //3000毫秒
登入後複製
這裡的回撥函數是function(){console.log('執行了回撥函數')}
,在滿足時間3秒後執行。
//1.建立非同步物件
var xhr=new XMLHttpRequest();
//2.繫結監聽事件(接收請求)
xhr.onreadystatechange=function(){
//此方法會被呼叫4次
//最後一次,readyState==4
//並且響應狀態碼為200時,才是我們要的響應結果 xhr.status==200
if(xhr.readyState==4 && xhr.status==200){
//把響應資料儲存到變數result中
var result=xhr.responseText;
console.log(result);
}
}
//3.開啟連結(建立請求)
xhr.open("get","/demo/ajaxDemo",true);
//4.傳送請求
xhr.send();
登入後複製
這裡的回撥函數是xhr.onreadystatechange
繫結的函數,在xhr.send()
傳送請求並拿到響應後執行。
2、非同步任務
與之相對應的概念是「同步任務」,同步任務在主執行緒上排隊執行,只有前一個任務執行完畢,才能執行下一個任務。非同步任務不進入主執行緒,而是進入非同步佇列,前一個任務是否執行完畢不影響下一個任務的執行。同樣,還拿定時器作為非同步任務舉例:
setTimeout(function(){
console.log('執行了回撥函數');
},3000)
console.log('111');
登入後複製
如果按照程式碼編寫的順序,應該先輸出「執行了回撥函數」,再輸出「111」。但實際輸出為:
這種不阻塞後面任務執行的任務就叫做非同步任務。
接下來讓我們看看什麼是回撥地獄。
根據前面我們可以得出一個結論:存在非同步任務的程式碼,不能保證能按照順序執行,那如果我們非要程式碼順序執行呢?
比如我要說一句話,語序必須是下面這樣的:武林要以和為貴,要講武德,不要搞窩裡鬥。
我必須要這樣操作,才能保證順序正確:
setTimeout(function () { //第一層
console.log('武林要以和為貴');
setTimeout(function () { //第二程
console.log('要講武德');
setTimeout(function () { //第三層
console.log('不要搞窩裡鬥');
}, 1000)
}, 2000)
}, 3000)
登入後複製
可以看到,程式碼中的回撥函數套回撥函數,居然套了3層,這種回撥函數中巢狀回撥函數的情況就叫做回撥地獄。
總結一下,回撥地獄就是為是實現程式碼順序執行而出現的一種操作,它會造成我們的程式碼可讀性非常差,後期不好維護。
那該如何解決回撥地獄呢?
Promise是js中的一個原生物件,是一種非同步程式設計的解決方案,可以替換掉傳統的回撥函數解決方案。
Promise建構函式接收一個函數作為引數,我們需要處理的非同步任務就解除安裝該函數體內,該函數的兩個引數是resolve,reject。非同步任務執行成功時呼叫resolve函數返回結果,反之呼叫reject。
Promise物件的then方法用來接收處理成功時響應的資料,catch方法用來接收處理失敗時相應的資料。
Promise的鏈式程式設計可以保證程式碼的執行順序,前提是每一次在than做完處理後,一定要return一個Promise物件,這樣才能在下一次then時接收到資料。
下面是範例程式碼:
function fn(str){
var p=new Promise(function(resolve,reject){
//處理非同步任務
var flag=true;
setTimeout(function(){
if(flag){
resolve(str)
}
else{
reject('操作失敗')
}
})
})
return p;
}
fn('武林要以和為貴')
.then((data)=>{
console.log(data);
return fn('要講武德');
})
.then((data)=>{
console.log(data);
return fn('不要搞窩裡鬥')
})
.then((data)=>{
console.log(data);
})
.catch((data)=>{
console.log(data);
})
登入後複製
但是Promise最大的問題就是程式碼冗餘,原來的非同步任務被Promise封裝一下,不管什麼操作都用than,就會導致一眼看過去全是then…then…then…,這樣也是不利於程式碼維護的。
所以下面的async/await 可以時程式碼看起來更像同步程式碼。
首先我們看async
關鍵字,他作為一個關鍵字放到宣告函數前面,表示該函數為一個非同步任務,不會阻塞後面函數的執行:
async function fn(){
return '不講武德';
}
console.log(fn());
登入後複製
可以看到async函數返回資料時自動封裝為一個Promise物件。
和Promise物件一樣,處理非同步任務時也可以按照成功和失敗來返回不同的資料,處理成功時用then方法來接收,失敗時用catch方法來接收資料:
async function fn() {
var flag = true;
if (flag) {
return '不講武德';
}
else{
throw '處理失敗'
}
}
fn()
.then(data=>{
console.log(data);
})
.catch(data=>{
console.log(data);
})
console.log('先執行我,表明async宣告的函數是非同步的');
登入後複製
當把flag設定為false是,執行結果為:async
關鍵字說完了,我們看看awai
關鍵字
//封裝一個返回promise的非同步任務
function fn(str) {
var p = new Promise(function (resolve, reject) {
var flag = true;
setTimeout(function () {
if (flag) {
resolve(str)
} else {
reject('處理失敗')
}
})
})
return p;
}
//封裝一個執行上述非同步任務的async函數
async function test(){
var res1=await fn('武林要以和為貴'); //await直接拿到fn()返回的promise的資料,並且賦值給res
var res2=await fn('要講武德');
var res3=await fn('不要搞窩裡鬥');
console.log(res1,res2,res3);
}
//執行函數
test();
登入後複製
結果為:
為什麼叫await
等待呢,因為當程式碼執行到async
函數中的await
時,程式碼就在此處等待不繼續往下執行,知道await
拿到Promise物件中resolve的資料,才繼續往下執行,這樣就保證了程式碼的執行順序,而且使非同步程式碼看起來更像同步程式碼。
總結一下,當我們寫程式碼遇到非同步回撥時,我們想讓非同步程式碼按照我們想要的順序執行,如果按照傳統的巢狀方式,就會出現回撥地獄,這樣的程式碼不利於維護,我們可以通過Promise物件進行鏈式程式設計來解決,這樣儘管可以解決問題,但是ES7給我們提供了更加舒適的async/await語法糖,可以使得非同步程式碼看起來更像是同步程式碼。
【相關推薦:、】
以上就是es6回撥地獄是什麼的詳細內容,更多請關注TW511.COM其它相關文章!