給你一個承諾 - 玩轉 AngularJS 的 Promise

瞭解Promise

在談論Promise以前咱們要了解一下一些額外的知識;咱們知道JavaScript語言的執行環境是「單線程」,所謂單線程,就是一次只可以執行一個任務,若是有多個任務的話就要排隊,前面一個任務完成後才能夠繼續下一個任務。javascript

這種「單線程」的好處就是實現起來比較簡單,容易操做;壞處就是容易形成阻塞,由於隊列中若是有一個任務耗時比較長,那麼後面的任務都沒法快速執行,或致使頁面卡在某個狀態上,給用戶的體驗不好。css

固然JavaScript提供了「異步模式」去解決上述的問題,關於「異步模式」JavaScript提供了一些實現的方法。html

  • 回調函數(callbacks)
  • 事件監聽
  • Promise對象

關於回調函數,你們應該都不陌生,好比下面的代碼(注:引用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 KowalQ庫的啓發,因此相似於那個庫,可是並無包含那個庫的所用功能。
  • $q是跟AngularJS$rootScope模板集成的,因此在AngularJS中執行和拒絕都很快。
  • $q promise是跟AngularJS模板引擎集成的,這意味着在視圖中找到任何Promise都會在視圖中被執行或者拒絕。

咱們能夠先使用$qdefer()方法建立一個deferred對象,而後經過deferred對象的promise屬性,將這個對象變成一個promise對象;這個deferred對象還提供了三個方法,分別是resolve(),reject(),notify()

下面咱們來經過代碼逐步地將上面的功能都實現,畢竟說得再多,不如你實實在在地把它們敲成代碼去實現。

Test1

咱們先經過一個同步的例子來建立一個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對象。

咱們給給promisethen方法傳遞了兩個處理函數,分別處理當promise被執行的時候以及promise被拒絕的時候所要進行的操做。

下面的一個if(){}else{}語句塊,包含執行和拒絕deferred promise,若是$scope.flagtrue,那麼咱們就會執行deferred promise,而後咱們給promise傳遞一個值,也多是一個對象,代表promise執行的結果。若是$scope.flagfalse,那麼咱們就會拒絕deferred promise,而後咱們給promise傳遞一個值,也多是一個對象,代表promise被拒絕的緣由。

如今回過頭來看看,promisethen方法,若是promise被執行,那麼它的參數中的第一個函數的result就表明了"you are lucky!"

咱們暫時用的是同步的模式,爲的是可以說明問題,後面將會使用異步的方法。

到這裏咱們能夠了解一下$qdefer()方法建立的對象具備哪些方法

  • resolve(value):用來執行deferred promisevalue能夠爲字符串,對象等。
  • reject(value):用來拒絕deferred promisevalue能夠爲字符串,對象等。
  • notify(value):獲取deferred promise的執行狀態,而後使用這個函數來傳遞它。
  • then(successFunc, errorFunc, notifyFunc):不管promise是成功了仍是失敗了,當結果可用以後,then都會馬上異步調用successFunc,或者'errorFunc',在promise被執行或者拒絕以前,notifyFunc可能會被調用0到屢次,以提供過程狀態的提示。
  • catch(errorFunc)
  • finally(callback)

Online Code Part1

經過使用then進行鏈式請求

咱們經過使用then方法來進行鏈式調用,這樣作的好處是,不管前一個任務或者說then函數是被執行或者拒絕了都不會影響後面的then函數的運行。

咱們能夠經過then建立一個執行鏈,它容許咱們中斷基於更多功能的應用流程,能夠藉此導向不一樣的的結果,這個中斷可讓咱們在執行鏈的任意時刻暫停後者推遲promise的執行。

Test2

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!");
            }
        }
}]);

Online Code Part2

咱們在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/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還有一個方法,你們有興趣的話能夠本身看看相關資料,我這裏就很少說了。。。

若是你以爲這篇文章哪裏說得不正確,歡迎你們指出來,一塊兒進步!^_^

相關文章
相關標籤/搜索