給你一個承諾,玩轉angularjs的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;
});
}]);
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
還有一個方法,大家有興趣的話可以自己看看相關資料,我這裡就不多說了。。。
如果你覺得這篇文章哪裡說得不正確,歡迎大家指出來,一起進步!^_^