Angular學(xué)習(xí)筆記(12)—promise

什么是promise

promise是一種用異步方式處理值(或非值)的方法。promise是對象,代表了一個函數(shù)最終可能的返回值或者拋出的異常。
習(xí)慣上,JS使用閉包或者回調(diào)來響應(yīng)非同步的有意義的數(shù)據(jù),比如頁面加載之后的XHR請求。我們可以跟數(shù)據(jù)進行交互,就好像它已經(jīng)返回了一樣,而不需要依賴于回調(diào)函數(shù)的觸發(fā)。
回調(diào)使得調(diào)用不一致,得不到保證,當(dāng)依賴于其他回調(diào)時,它們篡改代碼的流程,通常會讓調(diào)試變得非常難。每一步調(diào)用之后,都需要顯式處理錯誤。
在執(zhí)行異步方法時觸發(fā)一個函數(shù),然后期待一個回調(diào)能運行起來。與之不同的是,promise提供了另外一種抽象:這些函數(shù)返回promise對象。

 // 示例回調(diào)代碼
 User.get(fromId, {
     success: function(err, user) {
         if (err) return {error: err}; 
         user.friends.find(toId, function(err, friend) { 
             if (err) return {error: err}; 
             user.sendMessage(friend, message, callback); 
         });
     },
     fail: function(err) {
         return {error: err}
     }
 });

這個回調(diào)金字塔已經(jīng)失控了,而且我們還沒有加入健壯的錯誤處理代碼。此外,在被調(diào)用的回調(diào)內(nèi)部,也需要知道參數(shù)的順序。
剛才基于promise版本的代碼看上去更接近于:

User.get(fromId)
    .then(function(user) {
        return user.friends.find(toId);
    }, function(err) {
    // 沒找到用戶
})
.then(function(friend) {
    return user.sendMessage(friend, message); 
}, function(err) {
    // 用戶的朋友返回了異常
})
.then(function(success) {
    // user was sent the message
}, function(err) {
    // 發(fā)生錯誤了
});

代碼不僅僅是可讀性變高了,也更容易理解了。我們可以保證回調(diào)是一個值,而不用處理回調(diào)接口。

為什么使用promise

promise讓異步函數(shù)看上去像同步的?;谕胶瘮?shù),我們可以按照預(yù)期來捕獲返回值和異常值。
可以在程序中的任何時刻捕捉錯誤,并且繞過依賴于程序異常的后續(xù)代碼。
因此,使用promise的目的是:獲得功能組合和錯誤冒泡能力的同時,保持代碼異步運行的能力。
promise是頭等對象,自帶了一些約定。

  • 只有一個resolve或者reject會被調(diào)用到:
    • resolve被調(diào)用時,帶有一個履行值;
    • reject被調(diào)用時要帶一個拒絕原因。
  • 如果promise被執(zhí)行或者拒絕了,依賴于它們的處理程序仍然會被調(diào)用;
  • 處理程序總是會被異步調(diào)用。

此外,可以把promise串起來,并且允許代碼以通常運行的方式來處理。從一個promise冒出的異常會貫穿整個promise鏈。promise總是異步執(zhí)行的,可以放心使用,無需擔(dān)心它們會阻塞應(yīng)用的其他部分。

AngularJS中的promise

AngularJS的事件循環(huán)給予了AngularJS特有的能力,能在$rootScope.$evalAsync階段中執(zhí)行promise。promise會坐等$digest運行循環(huán)結(jié)束。這件事讓我們能毫無壓力地把promise的結(jié)果轉(zhuǎn)換到視圖上。它也能讓我們不加思考地把XHR調(diào)用的結(jié)果直接賦值到$scope對象的屬性上。
例子:從GitHub上返回一組針對AngularJS的開放pull請求。

<h1>Open Pull Requests for Angular JS</h1>
<ul ng-controller="DashboardController">
    <li ng-repeat="pr in pullRequests">
        {{ pr.title }}
    </li>
</ul>

如果有服務(wù)返回了一個promise,可以在.then()方法中與這個promise交互,它允許我們修改作用域上的任意變量,放置到視圖上,并且期望AngularJS會為我們執(zhí)行它。

angular.module('myApp', []) 
.controller('DashboardController', [
    '$scope', 'GithubService', 
        function($scope, UserService) { 
            // GithubService的getPullRequests()方法 
            // 返回了一個promise 
            User.getPullRequests(123) 
             .then(function(data) { 
                $scope.pullRequests = data.data; 
        });
}]);

當(dāng)對getPullRequests的異步調(diào)用返回時, 在.then()方法中就可以用$scope.pullRequests這個值了,然后它會更新$scope.pullRequests數(shù)組。

如何創(chuàng)建一個promise

在AngularJS中創(chuàng)建promise,可以使用內(nèi)置的$q服務(wù)。$q服務(wù)在它的deferred API中提供了一些方法。
首先,需要把$q服務(wù)注入到想要使用它的對象中。

angular.module('myApp',[]).factory('GithubService',['$q',function($q) { 
  // 現(xiàn)在就可以訪問到$q庫了
}]);

要創(chuàng)建一個deferred對象,可以調(diào)用defer()方法。

var deferred = $q.defer();

deferred對象暴露了三個方法,以及一個可以用于處理promisepromise屬性。

  • resolve(value):resolve函數(shù)用這個值來執(zhí)行deferred promise。
deferred.resolve({name: "Ari", username: "@auser"});
  • reject(reason)
    這個方法用一個原因來拒絕deferred promise。它等同于使用一個“拒絕”來執(zhí)行一個promise。
deferred.reject("Can't update user");
// 等同于
deferred.resolve($q.reject("Can't update user"));
  • notify(value):這個方法用promise的執(zhí)行狀態(tài)來進行響應(yīng)。

如果我們要從promise返回一個狀態(tài),可以使用notify()函數(shù)來傳送它。假設(shè)我們想要從一個promise創(chuàng)建多個長時間運行的請求??梢哉{(diào)用notify函數(shù)發(fā)回一個過程通知。

.factory('GithubService', function($q, $http) {
    // 從倉庫獲取事件
    var getEventsFromRepo = function() {
        // 任務(wù)
    }
    var service = {
        makeMultipleRequests: function(repos) { 
            var d = $q.defer(), 
                percentComplete = 0,
                output = [];
            for (var i = 0; i < repos.length; i++) { 
                output.push(getEventsFromRepo(repos[i])); 
                percentComplete = (i+1)/repos.length * 100; 
                d.notify(percentComplete);
            }
            d.resolve(output);
            return d.promise; 
        }
    }
    return service;
});

有了GithubService對象上的這個makeMultipleRequests()函數(shù),每次獲取和處理一個倉庫時,都會收到一個過程通知。
可以在我們對promise的使用中用到這個通知,在用promise時加上第三個函數(shù)調(diào)用。

.controller('HomeController',
    function($scope, GithubService) { 
        GithubService.makeMultipleRequests([ 
            'auser/beehive', 'angular/angular.js'
        ])
        .then(function(result) {
            // 處理結(jié)果
        }, function(err) {
            // 發(fā)生錯誤了
        }, function(percentComplete) { 
            $scope.progress = percentComplete; 
        });
});

可以在deferred對象上以屬性的方式訪問promisedeferred.promise
上面這個例子展示了如何創(chuàng)建一個函數(shù)用于響應(yīng)promise,看上去可能類似于下面這些GithubService上的方法。

angular.module('myApp', [])
    .factory('GithubService', [
        '$q', '$http',
        function($q, $http) {
            var getPullRequests = function() {
                var deferred = $q.defer();
                // 從Github獲取打開的angularjs pull請求列表 
                $http.get('https://api.github.com/repos/angular/angular.js/pulls') 
                .success(function(data) { 
                    deferred.resolve(data);
                })
                .error(function(reason) { 
                    deferred.reject(reason);
                })
                return deferred.promise;
            }
            return { // 返回工廠對象 
                getPullRequests: getPullRequests 
            };
}]);

現(xiàn)在我們就可以用promise API來跟getPullRequests() promise交互。在上面這個service的實例中,可以用兩種不同方式跟promise交互。

  • then(successFn,errFn,notifyFn)
    無論promise成功還是失敗了,當(dāng)結(jié)果可用之后,then都會立刻異步調(diào)用successFn或者errFn。這個方法始終用一個參數(shù)來調(diào)用回調(diào)函數(shù):結(jié)果,或者是拒絕的理由。
    promise被執(zhí)行或者拒絕之前,notifyFn回調(diào)可能會被調(diào)用0到多次,以提供過程狀態(tài)的提示。
    then()方法總是返回一個新的promise,可以通過successFn或者errFn這樣的返回值執(zhí)行或者被拒絕。它也能通過notifyFn提供通知。
  • catch(errFn)
    這個方法就只是個幫助函數(shù),能讓我們用.catch(function(reason){})取代err回調(diào)。
$http.get('/repos/angular/angular.js/pulls')
.catch(function(reason) {
deferred.reject(reason);
});
  • finally(callback)
    finally方法允許我們觀察promise的履行或者拒絕,而無需修改結(jié)果的值。當(dāng)我們需要釋放一個資源,或者是運行一些清理工作,不管promise是成功還是失敗時,這個方法會很有用。
    我們不能直接調(diào)用這個方法,因為finally是IE中JS的一個保留字。糾結(jié)到最后,只好這樣調(diào)用它了:
promise['finally'](function() {});

AngularJS的$q deferred對象是可以串成鏈的,這樣即使是then,返回的也是一個promise。這個promise一被執(zhí)行,then返回的promise就已經(jīng)是resolved或者rejected的了。
這些promise也就是AngularJS能支持$http攔截器的原因。
$q服務(wù)類似于原始的Kris Kowal的Q庫:
(1) $q是跟Angular的$rootScope模型集成的,所以在Angular中,執(zhí)行和拒絕都很快。
(2) $q promise是跟Angular模板引擎集成的,這意味著在視圖中找到的任何promise都會在視圖中被執(zhí)行或者拒絕。
(3) $q很小,所以沒有包含Q庫的完整功能。

鏈?zhǔn)秸埱?/h2>

then方法在初始promise被執(zhí)行之后,返回一個新的派生promise。這種返回形式可以把另一個then接在初始的then方法結(jié)果之后。

 // 一個響應(yīng)promise的服務(wù) 
 GithubService.then(function(data) {
     var events = [];
     for (var i = 0; i < data.length; i++) { 
         events.push(data[i].events);
     }
     return events;
 }).then(function(events) {
     $scope.events = events;
});

在本例中,我們可以創(chuàng)建一個執(zhí)行鏈,它允許我們中斷基于更多功能的應(yīng)用流程,可以籍此導(dǎo)向不同的結(jié)果。這個中斷能讓我們在執(zhí)行鏈的任意時刻暫停或者推遲promise的執(zhí)行。這個中斷也是$http服務(wù)實現(xiàn)請求和響應(yīng)攔截器的方式。
$q庫自帶了幾個不同的有用方法。

all(promises)

??如果我們有多個promise,想要把它們合并成一個,可以使用$q.all(promises)方法來把它
們合并成一個promise。這個方法帶有一個參數(shù)。

  • promises(數(shù)組或者promise對象):一個promise數(shù)組或者promisehash

all()方法返回單個promise,會執(zhí)行一個數(shù)組或者一個散列的值。每個值會響應(yīng)promise散列中的相同序號或者鍵。如果任意一個promise被拒絕了,結(jié)果的promise也會被拒絕。

defer()

defer()方法創(chuàng)建了一個deferred對象,它沒有參數(shù),返回deferred對象的一個實例。

reject(reason)

這個方法創(chuàng)建了一個promise,被以某一原因拒絕執(zhí)行了。它專門用于讓我們能在一個promise鏈中轉(zhuǎn)發(fā)拒絕的promise,類似JS中的throw。在同樣意義上,我們能在JS中捕獲一個異常,也能夠轉(zhuǎn)發(fā)這個拒絕,我們需要把這個錯誤重新拋出??梢酝ㄟ^$q.reject(reason)來做到這點。
這個方法帶有單個參數(shù):

  • reason(常量、字符串、異常、對象):拒絕的原因。

reject()方法返回一個已經(jīng)用某個原因拒絕的promise

when(value)

when()函數(shù)把一個可能是值或者能接著thenpromise包裝成一個$q promise。這樣我們就能處理一個可能是也可能不是promise的對象。
when()函數(shù)有一個參數(shù):value,該參數(shù)是個值,或者是promise。
when()函數(shù)返回了一個promise,我們可以像使用其他promise一樣使用它。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容