玩轉 AngularJS 的 Promise

瞭解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 KowalQ庫的啓發,所以類似於那個庫,但是並沒有包含那個庫的所用功能。
  • $q是跟AngularJS$rootScope模板集成的,所以在AngularJS中執行和拒絕都很快。
  • $q promise是跟AngularJS模板引擎集成的,這意味着在視圖中找到任何Promise都會在視圖中被執行或者拒絕。

我們可以先使用$qdefer()方法創建一個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對象。

我們給給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代碼

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

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

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還有一個方法,大家有興趣的話可以自己看看相關資料,我這裏就不多說了。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章