瞭解Promise
在談論Promise
之前我們要了解一下一些額外的知識;我們知道JavaScript語言的執行環境是“單線程”,所謂單線程,就是一次只能夠執行一個任務,如果有多個任務的話就要排隊,前面一個任務完成後纔可以繼續下一個任務。
這種“單線程”的好處就是實現起來比較簡單,容易操作;壞處就是容易造成阻塞,因爲隊列中如果有一個任務耗時比較長,那麼後面的任務都無法快速執行,或導致頁面卡在某個狀態上,給用戶的體驗很差。
當然JavaScript提供了“異步模式”去解決上述的問題,關於“異步模式”JavaScript提供了一些實現的方法。
- 回調函數(callbacks)
- 事件監聽
- Promise對象
關於回調函數,大家應該都不陌生,比如下面的代碼(注:引用Leancloud上面的一點代碼):
AV.User.logIn("myname", "mypass", {
success: function(user) {
// Do stuff after successful login.
},
error: function(user, error) {
// The login failed. Check error to see why.
}
});
用戶通過用戶名和密碼來進行登錄,如果登陸成功的話,會在success
這個模塊進行處理,如果登陸失敗的話,就會在error
這個模塊進行處理。
當我們需要處理的任務不是很多的情況下,使用回調函數還是可以應付的,也沒有太大的問題,但是當我們需要處理的任務比較多的時候,使用回調函數的弊端越來越明顯了;首先,回調使得調用不一致,得不到保證;當依賴於其它回調時,它們篡改代碼的流程,是調試變得異常艱難,每一步調用之後都需要顯式的處理錯誤;最後,過多的回調使得代碼的可讀性和可維護性都變得很差,所以越來越多的程序員選擇使用Promise
去處理異步模式。
關於Promise
我們會在下面進行詳細的說明。
Promise
是什麼
Promise
是一種異步方式處理值(或者非值)的方法,promise
是對象,代表了一個函數最終可能的返回值或者拋出的異常。
在與遠程對象打交道時,Promise
會非常有用,可以把它們看作遠程對象的一個代理。
點擊下面的鏈接可以查看Promise
更多的信息
使用Promise
的理由
- 使用
Promise
可以讓我們逃脫回調地獄,使我們的代碼看起來像是同步的那樣。 - 可以在程序中的任何位置捕捉錯誤,並且繞過依賴於程序異常的的後續代碼,獲得功能組合和錯誤冒泡的能力,最重要的是保持了異步運行的能力。
- 使我們的代碼的可讀性與可維護性都變得很好。
如何在AngularJS
中使用Promise
要在AngularJS
中使用Promise
,要使用AngularJS
的內置服務$q
。
$q
服務受到Kris Kowal的Q
庫的啓發,所以類似於那個庫,但是並沒有包含那個庫的所用功能。$q
是跟AngularJS
的$rootScope
模板集成的,所以在AngularJS
中執行和拒絕都很快。$q promise
是跟AngularJS
模板引擎集成的,這意味着在視圖中找到任何Promise
都會在視圖中被執行或者拒絕。
我們可以先使用$q
的defer()
方法創建一個deferred
對象,然後通過deferred
對象的promise
屬性,將這個對象變成一個promise
對象;這個deferred
對象還提供了三個方法,分別是resolve()
,reject()
,notify()
。
下面我們來通過代碼逐步地將上面的功能都實現,畢竟說得再多,不如你實實在在地把它們敲成代碼去實現。
Test1
我們先通過一個同步的例子來創建一個promise
對象。
HTML代碼:
<div ng-app="MyApp">
<div ng-controller="MyController">
<label for="flag">成功
<input id="flag" type="checkbox" ng-model="flag" /><br/>
</label>
<hr/>
<button ng-click="handle()">點擊我</button>
</div>
</div>
JS代碼:
angular.module("MyApp", [])
.controller("MyController", ["$scope", "$q", function ($scope, $q) {
$scope.flag = true;
$scope.handle = function () {
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function (result) {
alert("Success: " + result);
}, function (error) {
alert("Fail: " + error);
});
if ($scope.flag) {
deferred.resolve("you are lucky!");
} else {
deferred.reject("sorry, it lost!");
}
}
}]);
我們來詳細的分析一下上面的代碼,我們在html
頁面上添加了一個checkbox
,一個button
目的是爲了當我們選中checkbox
和不選中checkbox
時,點擊下面的按鈕會彈出不同的內容。
var deferred = $q.defer()
這段代碼創建了一個deferred
對象,我們然後利用var
promise = deferred.promise
創建了一個promise
對象。
我們給給promise
的then
方法傳遞了兩個處理函數,分別處理當promise
被執行的時候以及promise
被拒絕的時候所要進行的操作。
下面的一個if(){}else{}
語句塊,包含執行和拒絕deferred
promise
,如果$scope.flag
爲true
,那麼我們就會執行deferred
promise
,然後我們給promise
傳遞一個值,也可能是一個對象,表明promise
執行的結果。如果$scope.flag
爲false
,那麼我們就會拒絕deferred
promise
,然後我們給promise
傳遞一個值,也可能是一個對象,表明promise
被拒絕的原因。
現在回過頭來看看,promise
的then
方法,如果promise
被執行,那麼它的參數中的第一個函數的result
就代表了"you
are lucky!"
我們暫時用的是同步的模式,爲的是能夠說明問題,後面將會使用異步的方法。
到這裏我們可以瞭解一下$q
的defer()
方法創建的對象具有哪些方法
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)
通過使用then
進行鏈式請求
我們通過使用then
方法來進行鏈式調用,這樣做的好處是,無論前一個任務或者說then
函數是被執行或者拒絕了都不會影響後面的then
函數的運行。
我們可以通過then
創建一個執行鏈,它允許我們中斷基於更多功能的應用流程,可以藉此導向不同的的結果,這個中斷可以讓我們在執行鏈的任意時刻暫停後者推遲promise
的執行。
Test2
HTML代碼
<div ng-app="MyApp">
<div ng-controller="MyController">
<label for="flag">成功
<input id="flag" type="checkbox" ng-model="flag" /><br/>
</label>
<div ng-cloak>
{{status}}
</div>
<hr/>
<button ng-click="handle()">點擊我</button>
</div>
</div>
JS代碼:
angular.module("MyApp", [])
.controller("MyController", ["$scope", "$q", function ($scope, $q) {
$scope.flag = true;
$scope.handle = function () {
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function (result) {
result = result + "you have passed the first then()";
$scope.status = result;
return result;
}, function (error) {
error = error + "failed but you have passed the first then()";
$scope.status = error;
return error;
}).then(function (result) {
alert("Success: " + result);
}, function (error) {
alert("Fail: " + error);
})
if ($scope.flag) {
deferred.resolve("you are lucky!");
} else {
deferred.reject("sorry, it lost!");
}
}
}]);
我們在Part1代碼的基礎上添加了一些代碼,在原來的promise
的鏈條上新添加了一個then()
處理函數,目的就是爲了創建一個執行連,看看在這條執行連上,promise
是如何被執行的。
需要注意的一點是,在第一個then()
方法中,我們在第一個successFunc
函數中將result
的值進行了改變,在第二個errorFunc
函數中對error
的值也進行了改變。
因爲這個promise
對象是貫穿整個執行鏈條的,所以在第一個then()
方法中對其值進行改變必然會反映到後面的then()
方法中
一個異步模式的實例
Test3
第三個例子,我們創建了一個服務,然後在這個服務中創建了一個promise
,服務的目的就是爲了拉取github
上面關於angularjs
一些pull
的數據,詳細的代碼可以看下面
下面的例子包含的部分有點多,因爲我是在以前的例子上做的改動,大家可以只看promise
這部分。
目錄結構:
- MyApp
- js
- app.js
- controller.js
- service.js
- views
- home.html
- index.html
- js
js/app.js
angular.module("MyApp", ["ngRoute","MyController", "MyService"])
.config(["$routeProvider", function($routeProvider){
$routeProvider
.when('/',{
templateUrl: "views/home.html",
controller: "IndexController"
});
}]);
js/controller.js
angular.module("MyController", [])
.controller("IndexController", ["$scope", "githubService", function($scope, githubService){
$scope.name = "dreamapple";
$scope.show = true;
githubService.getPullRequests().then(function(result){
$scope.data = result;
},function(error){
$scope.data = "error!";
},function(progress){
$scope.progress = progress;
$scope.show = false;
});
}]);
js/service.js
angular.module("MyService", [])
.factory('githubService', ["$q", "$http", function($q, $http){
var getPullRequests = function(){
var deferred = $q.defer();
var promise = deferred.promise;
var progress;
$http.get("https://api.github.com/repos/angular/angular.js/pulls")
.success(function(data){
var result = [];
for(var i = 0; i < data.length; i++){
result.push(data[i].user);
progress = (i+1)/data.length * 100;
deferred.notify(progress);
}
deferred.resolve(result);
})
.error(function(error){
deferred.reject(error);
});
return promise;
}
return {
getPullRequests: getPullRequests
};
}]);
views/home.html
<h1>{{name}}</h1>
<h2>Progress: {{progress}}</h2>
<h3 ng-show="show">Please wait a moment...</h3>
<p ng-repeat="person in data">{{person.login}}</p>
index.html
<!-- 不把下面的註釋掉會出現問題,我是指上傳到segmentfault上 -->
<!-- <head>
<meta charset="UTF-8">
<title>Route</title>
<script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.1/angular.js"></script>
<script src="../node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controller.js"></script>
<script src="js/service.js"></script>
</head> -->
<body ng-app="MyApp">
<header>
<h1>Header</h1>
<hr/>
</header>
<div ng-view>
</div>
<footer>
<hr/>
<h1>Footer</h1>
</footer>
</body>
終於可以伸個懶腰了,關於$q
還有一個方法,大家有興趣的話可以自己看看相關資料,我這裏就不多說了。。。