Angularjs Promise 解決非同步獲取資料導致return返回為空的問題
最近在開發專案的時候。我在service中請求資料返回給控制器的時候,由於資料是非同步請求的,這裡需要知道javascript的執行環境是單執行緒的,一次只能執行一個任務,但是單執行緒壞處就是如果前一個任務執行時間較長就會導致整個頁面的阻塞,因此javascript提供了非同步請求,使任務可以不用等待上一個任務執行完成。但就是這個非同步的請求,導致我的data資料還沒返回就已經return資料到控制層了,從而返回值為空。
goodsService
app.factory('goodsService', function($http,$q) {
var goodsData=111;
function get() {
$http({
method:'get',
url:'test/goods.json'
}).success(function(data,status,headers,config){
console.log('get success...');
console.log(goodsData);
goodsData=data;
return goodsData;
}).error(function (data,status,headers,config){
console.log('get error...');
})
}
function set(data) {
$http({
method:'post',
url:'test/goods.json'
}).success(function(data,status,headers,config){
goodsData=data;
console.log('get success...' );
console.log(data);
}).error(function(data,status,headers,config){
console.log('get error...');
})
}
return {
set: set,
get: get
}
});
controller.js
$scope.goods=goodsService.get();
console.log('scope.goods='+$scope.goods);
當然很多人都有很多種解決辦法。比如說函式引用:
$.get(/test/goods.json', function(data) {
return data;
});
當然都是可行的。但是當處理比較複雜的多個非同步進行的時候那就真是看的頭暈眼花。因此Promise應運而生。
Promise的身份之謎
Promise有兩部分:
-
Deferred定義工作單元,用來定義工作單元的開始,處理和結束三部分
-
Promise接收Deferred返回的資料。有狀態和控制代碼。Promise 不同於回撥的很重要的一個點是,你可以在 Promise 狀態變成執行(resolved)之後追加處理控制代碼。這就允許你傳輸資料,而忽略它是否已經被應用獲取,然後快取它,等等之類的操作,因此你可以對資料執行操作,而不管它是否已經或者即將可用。
在Angularjs使用Promise的時候需要用到內建服務$q。
-
$q服務受到Kris Kowal的Q庫的啟發,所以類似於那個庫,但是並沒有包含那個庫的所用功能。
-
$q是跟AngularJS的$rootScope模板整合的,所以在AngularJS中執行和拒絕都很快。 $q
-
promise是跟AngularJS模板引擎整合的,這意味著在檢視中找到任何Promise都會在檢視中被執行或者拒絕。$q實現了上面提到的所有的Deferred和Promise方法。
$q.defer()提供給我們一個建立Deferred物件的方法。這個Deffered物件有個promise屬性,這個屬性帶有6個方法:
-
resolve(value):用來執行deferred promise,value可以為字串,物件等。
-
reject(value):用來拒絕deferred promise,value可以為字串,物件等。
-
notify(value):獲取deferred promise的執行狀態,然後使用這個函式來傳遞它。
-
then(successFunc, errorFunc,notifyFunc):無論promise是成功了還是失敗了,當結果可用之後,then都會立刻非同步呼叫successFunc,或者'errorFunc',在promise被執行或者拒絕之前,notifyFunc可能會被呼叫0到多次,以提供過程狀態的提示。
-
catch(errorFunc)
-
finally(callback)更加形象點。就比如說我現在遇到的這個問題。當我的goodsService請求商品資料的時候,我先用$q.defer()建立了一個deferred物件。然後通過該物件的promise屬性獲取到一個promise物件。這時我進行資料請求,定義請求成功和請求失敗的計劃分別是deferred.resolve(data)和deferred.reject()。然後將這個promise返回給請求service的控制層。控制層通過.then建立一個執行鏈,它允許我們中斷基於更多功能的應用流程,可以藉此導向不同的的結果,再來進行不同的操作。
goodsService
app.factory('goodsService', function($http,$q) {
function get() {
var deferred=$q.defer();
var promise=deferred.promise;
$http({
method:'get',
url:'test/goods.json'
}).success(function(data,status,headers,config){
deferred.resolve(data);//執行成功
}).error(function(data,status,headers,config){
deferred.reject();//執行失敗
})
console.log('return promise');
return promise;
}
function set(data) {
$http({
method:'post',
url:'test/goods.json'
}).success(function(data,status,headers,config){
deferred.resolve(data);//執行成功
console.log('get success...');
}).error(function(data,status,headers,config){
console.log('get error...');
})
}
return {
set: set,
get: get
}
});
productListCtrl
goodsService.get().then(function(result){
console.log('goodsService get successed');
$scope.goods=result;
console.log('scope.goods1='+$scope.goods);
},function(){
console.log('goodsService get error');
});
console.log('scope.goods2='+$scope.goods);
});
資料顯示正常。也可以從下面的scope.goods1和scope.goods2看出promise的特性,在等待返回選擇不同計劃的時候不會阻塞其他的任務執行。