Promise
在談論Promise
以前咱們要了解一下一些額外的知識;咱們知道JavaScript語言的執行環境是「單線程」,所謂單線程,就是一次只可以執行一個任務,若是有多個任務的話就要排隊,前面一個任務完成後才能夠繼續下一個任務。javascript
這種「單線程」的好處就是實現起來比較簡單,容易操做;壞處就是容易形成阻塞,由於隊列中若是有一個任務耗時比較長,那麼後面的任務都沒法快速執行,或致使頁面卡在某個狀態上,給用戶的體驗不好。css
固然JavaScript提供了「異步模式」去解決上述的問題,關於「異步模式」JavaScript提供了一些實現的方法。html
關於回調函數,你們應該都不陌生,好比下面的代碼(注:引用Leancloud上面的一點代碼):java
javascriptAV.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
這個模塊進行處理。node
當咱們須要處理的任務不是不少的狀況下,使用回調函數仍是能夠應付的,也沒有太大的問題,可是當咱們須要處理的任務比較多的時候,使用回調函數的弊端愈來愈明顯了;首先,回調使得調用不一致,得不到保證;當依賴於其它回調時,它們篡改代碼的流程,是調試變得異常艱難,每一步調用以後都須要顯式的處理錯誤;最後,過多的回調使得代碼的可讀性和可維護性都變得不好,因此愈來愈多的程序員選擇使用Promise
去處理異步模式。git
關於Promise
咱們會在下面進行詳細的說明。程序員
Promise
是什麼Promise
是一種異步方式處理值(或者非值)的方法,promise
是對象,表明了一個函數最終可能的返回值或者拋出的異常。angularjs
在與遠程對象打交道時,Promise
會很是有用,能夠把它們看做遠程對象的一個代理。github
點擊下面的連接能夠查看Promise
更多的信息segmentfault
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()
。
下面咱們來經過代碼逐步地將上面的功能都實現,畢竟說得再多,不如你實實在在地把它們敲成代碼去實現。
咱們先經過一個同步的例子來建立一個promise
對象。
HTML代碼:
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代碼:
javascriptangular.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
的執行。
HTML代碼
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代碼:
javascriptangular.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()
方法中
第三個例子,咱們建立了一個服務,而後在這個服務中建立了一個promise
,服務的目的就是爲了拉取github
上面關於angularjs
一些pull
的數據,詳細的代碼能夠看下面
下面的例子包含的部分有點多,由於我是在之前的例子上作的改動,你們能夠只看promise
這部分。
目錄結構:
js/app.js
javascriptangular.module("MyApp", ["ngRoute","MyController", "MyService"]) .config(["$routeProvider", function($routeProvider){ $routeProvider .when('/',{ templateUrl: "views/home.html", controller: "IndexController" }); }]);
js/controller.js
javascriptangular.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
javascriptangular.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
還有一個方法,你們有興趣的話能夠本身看看相關資料,我這裏就很少說了。。。
若是你以爲這篇文章哪裏說得不正確,歡迎你們指出來,一塊兒進步!^_^