Angular非同步變同步處理
1. 背景
在前端專案中,經常會遇到頁面有多個介面,後一個介面引數需要從前一個介面的返回資料中獲取,這就存在介面必須按順序一個一個執行。而Angular提供的Http服務請求介面都是非同步請求,因此通常情況下會寫成如下的程式碼:
funA(arg1,arg2,function(){
funcB(arg1,arg2,function(){
funcC(arg1,arg2,function(){
xxxx....
})
})
});
這不僅造成頁面程式碼混亂不易維護,也讓開發者無法追蹤程式碼的執行。本身巢狀就已經不容易理解,加上不知何時才能觸發回撥,這就相當於雪上加霜。
2. Promise
針對這中現象,Angular推出了Promise規範,它可以幫助開發者將非同步變成同步,是一種對執行結果不確定的一種預先定義。例如以下程式碼中,當getData方法內的物件成功執行,就會呼叫success方法,如果失敗,就會呼叫error方法。另外promise除了then方法之外,還有catch()與finally()方法。
defer. getData()
.then( funcSuccess (){}, funcError (){} )
.catch( function(){})
.finally( function(){});
Finally()方法一般用來處理不管promise
3. $q服務
q服務是AngularJs中自己封裝實現的一種Promise實現,一般有如下三個常用方法:
- defer() :建立一個deferred物件,這個物件可以執行幾個常用的方法,比如resolve,reject,notify等;
- all() :傳入Promise的陣列,批量執行,返回一個Promise物件;
- when() :傳入一個不確定的引數,如果符合Promise標準,就返回一個Promise物件。
3.1. defer() 方法
在$q服務中,用defer()方法建立一個deferred物件,然後這個物件可以呼叫resolve方法定義成功狀態,使用reject方法定義失敗狀態,並且可以在這些方法中傳遞引數,一般介面返回的資料都是用這些方法傳遞出去。最後通過deferred物件 .promise來返回一個promise物件,來定義then方法。then方法中有三個引數,分別是成功回撥、失敗回撥、狀態變更回撥。
定義方法:
function funA(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funA success');
}else{
defer.reject('funA error');
}
return defer.promise;
}
方法呼叫(返回成功):
funA(3).then(function(success){
console.log(success);
},function(err){
console.log(err);
})
結果:
方法呼叫(返回失敗):
funA(8).then(function(success){
console.log(success);
},function(err){
console.log(err);
})
結果:
3.2. 鏈式呼叫
瞭解了defer()方法與then()方法,接下來就能解決多個介面巢狀呼叫的問題了。
一般處理多個介面需要巢狀執行時,可採用angular的鏈式呼叫,即:
FunA.then().then().then();
在then方法中return 要呼叫的下一個介面。如下funA、funB、funC都是返回了promise物件的方法,如果執行順序為funA、funB、funC,則可用鏈式呼叫。
function funA(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funA success');
}else{
defer.reject('funA error');
}
return defer.promise;
}
function funB(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funB success');
}else{
defer.reject('funB error');
}
return defer.promise;
}
function funC(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funC success');
}else{
defer.reject('funC error');
}
return defer.promise;
}
方法呼叫:
funA(3).then(function(success){
console.log(success);
return funB(3);
}).then(function(success){
console.log(success);
return funC(3);
}).then(function(success){
console.log(success);
},function(err){
console.log(err);
});
結果:
上述方法呼叫中,then()方法中只寫了成功引數的方法,如果其中一個方法呼叫返回失敗,則該方法後邊的方法都不會再執行,也就是隻有前一個方法呼叫成功,才能呼叫後邊的方法。如下為其中一個方法失敗的示例,funB方法呼叫返回失敗,則不會再執行funC,而錯誤的資訊會在最後捕捉錯誤資訊的引數方法中獲取。
方法呼叫:
funA(3).then(function(success){
console.log(success);
return funB(8);
}).then(function(success){
console.log(success);
return funC(3);
}).then(function(success){
console.log(success);
},function(err){
console.log(err); //列印funB()的錯誤資訊
});
結果:
如果在某些場景中,等前一個方法呼叫完畢,而不管這個方法是否呼叫成功,都要繼續呼叫後邊的方法,則需要在then()方法中增加錯誤回撥並return 下一個執行的promise。如下:
funA(3).then(function(success){
console.log(success);
return funB(8);
},function(err){
console.log(err);
return funB(8);
}).then(function(success){
console.log(success);
return funC(3);
},function(err){
console.log(err);
return funC(3);
}).then(function(success){
console.log(success);
},function(err){
console.log(err);
});
結果:
3.3. all() 方法
all()方法可以把多個promise的數組合併成一個。當所有的promise執行成功後,會執行後面的回撥,回撥中的引數,是每個promise執行的結果。
function funA(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funA success');
}else{
defer.reject('funA error');
}
return defer.promise;
}
function funB(num){
var defer = $q.defer();
if(num<5){
defer.resolve('funB success');
}else{
defer.reject('funB error');
}
return defer.promise;
}
方法呼叫:
$q.all([
funA(3),
funB(3)
])
.then(function(success){
console.log(success);
},function(err){
console.log(err);
})
結果:
all() 方法中的引數可以是上面的陣列形式,也可以是json格式。相應的,如果引數是陣列格式,那麼返回的資料格式也是陣列格式;如果引數是json格式,那麼返回的資料格式也是json格式。json引數示例如下:
$q.all(
{
funA:DeferService.funA(3),
funB:DeferService.funB(3)
})
.then(function(success){
console.log(success);
},function(err){
console.log(err);
})
結果:
上面兩個示例展示的都是獲取資料成功的情況。而當all()方法的引數中存在一個promise失敗,則整個任務都失敗,返回的資料也只會返回該失敗引數的失敗資訊。
方法呼叫:
$q.all(
{
funA:DeferService.funA(8), //呼叫失敗
//正常情況下是呼叫成功,但由於funA呼叫失敗,固該方法也是失敗
funB:DeferService.funB(3)
})
.then(function(success){
console.log(success);
},function(err){
console.log(err);
})
結果:
3.4. when() 方法
when()方法中的引數可以是一個值,也可以是一個promise,這個方法是把傳入的引數處理包裝成一個promise。這個方法一般在你不確定所處理的物件或者呼叫的方法是不是一個promise時使用。
例如在下面的例子中,val=10,並不是一個promise,但是經過when()方法包裝之後,可以通過.then()方法去呼叫,像處理平常的promise一樣。
var val=10;
$q.when(val)
.then(function(success){
console.log(success);
},function(err){
console.log(err);
});
結果: