欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Angular学习笔记(12)—promise

什么是promise

promise是一种用异步方式处理值(或非值)的方法。promise是对象,代表了一个函数最终可能的返回值或者抛出的异常。
习惯上,JS使用闭包或者回调来响应非同步的有意义的数据,比如页面加载之后的XHR请求。我们可以跟数据进行交互,就好像它已经返回了一样,而不需要依赖于回调函数的触发。
回调使得调用不一致,得不到保证,当依赖于其他回调时,它们篡改代码的流程,通常会让调试变得非常难。每一步调用之后,都需要显式处理错误。
在执行异步方法时触发一个函数,然后期待一个回调能运行起来。与之不同的是,promise提供了另外一种抽象:这些函数返回promise对象。

 // 示例回调代码
 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}
     }
 });

这个回调金字塔已经失控了,而且我们还没有加入健壮的错误处理代码。此外,在被调用的回调内部,也需要知道参数的顺序。
刚才基于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) {
    // 发生错误了
});

代码不仅仅是可读性变高了,也更容易理解了。我们可以保证回调是一个值,而不用处理回调接口。

为什么使用promise

promise让异步函数看上去像同步的。基于同步函数,我们可以按照预期来捕获返回值和异常值。
可以在程序中的任何时刻捕捉错误,并且绕过依赖于程序异常的后续代码。
因此,使用promise的目的是:获得功能组合和错误冒泡能力的同时,保持代码异步运行的能力。
promise是头等对象,自带了一些约定。

  • 只有一个resolve或者reject会被调用到:

    • resolve被调用时,带有一个履行值;

    • reject被调用时要带一个拒绝原因。

  • 如果promise被执行或者拒绝了,依赖于它们的处理程序仍然会被调用;

  • 处理程序总是会被异步调用。

此外,可以把promise串起来,并且允许代码以通常运行的方式来处理。从一个promise冒出的异常会贯穿整个promise链。promise总是异步执行的,可以放心使用,无需担心它们会阻塞应用的其他部分。

AngularJS中的promise

AngularJS的事件循环给予了AngularJS特有的能力,能在$rootScope.$evalAsync阶段中执行promisepromise会坐等$digest运行循环结束。这件事让我们能毫无压力地把promise的结果转换到视图上。它也能让我们不加思考地把XHR调用的结果直接赋值到$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>

如果有服务返回了一个promise,可以在.then()方法中与这个promise交互,它允许我们修改作用域上的任意变量,放置到视图上,并且期望AngularJS会为我们执行它。

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

当对getPullRequests的异步调用返回时, 在.then()方法中就可以用$scope.pullRequests这个值了,然后它会更新$scope.pullRequests数组。

如何创建一个promise

在AngularJS中创建promise,可以使用内置的$q服务。$q服务在它的deferred API中提供了一些方法。
首先,需要把$q服务注入到想要使用它的对象中。

angular.module('myApp',[]).factory('GithubService',['$q',function($q) { 
  // 现在就可以访问到$q库了
}]);

要创建一个deferred对象,可以调用defer()方法。

var deferred = $q.defer();

deferred对象暴露了三个方法,以及一个可以用于处理promisepromise属性。

  • resolve(value):resolve函数用这个值来执行deferred promise

deferred.resolve({name: "Ari", username: "@auser"});
  • reject(reason)
    这个方法用一个原因来拒绝deferred promise。它等同于使用一个“拒绝”来执行一个promise

deferred.reject("Can't update user");
// 等同于
deferred.resolve($q.reject("Can't update user"));
  • notify(value):这个方法用promise的执行状态来进行响应。

如果我们要从promise返回一个状态,可以使用notify()函数来传送它。假设我们想要从一个promise创建多个长时间运行的请求。可以调用notify函数发回一个过程通知。

.factory('GithubService', function($q, $http) {
    // 从仓库获取事件
    var getEventsFromRepo = function() {
        // 任务
    }
    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()函数,每次获取和处理一个仓库时,都会收到一个过程通知。
可以在我们对promise的使用中用到这个通知,在用promise时加上第三个函数调用。

.controller('HomeController',
    function($scope, GithubService) { 
        GithubService.makeMultipleRequests([ 
            'auser/beehive', 'angular/angular.js'
        ])
        .then(function(result) {
            // 处理结果
        }, function(err) {
            // 发生错误了
        }, function(percentComplete) { 
            $scope.progress = percentComplete; 
        });
});

可以在deferred对象上以属性的方式访问promisedeferred.promise
上面这个例子展示了如何创建一个函数用于响应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 
            };
}]);

现在我们就可以用promise API来跟getPullRequests() promise交互。在上面这个service的实例中,可以用两种不同方式跟promise交互。

  • then(successFn,errFn,notifyFn)
    无论promise成功还是失败了,当结果可用之后,then都会立刻异步调用successFn或者errFn。这个方法始终用一个参数来调用回调函数:结果,或者是拒绝的理由。
    promise被执行或者拒绝之前,notifyFn回调可能会被调用0到多次,以提供过程状态的提示。
    then()方法总是返回一个新的promise,可以通过successFn或者errFn这样的返回值执行或者被拒绝。它也能通过notifyFn提供通知。

  • catch(errFn)
    这个方法就只是个帮助函数,能让我们用.catch(function(reason){})取代err回调。

$http.get('/repos/angular/angular.js/pulls')
.catch(function(reason) {
deferred.reject(reason);
});
  • finally(callback)
    finally方法允许我们观察promise的履行或者拒绝,而无需修改结果的值。当我们需要释放一个资源,或者是运行一些清理工作,不管promise是成功还是失败时,这个方法会很有用。
    我们不能直接调用这个方法,因为finally是IE中JS的一个保留字。纠结到最后,只好这样调用它了:

promise['finally'](function() {});

AngularJS的$q deferred对象是可以串成链的,这样即使是then,返回的也是一个promise。这个promise一被执行,then返回的promise就已经是resolved或者rejected的了。
这些promise也就是AngularJS能支持$http拦截器的原因。
$q服务类似于原始的Kris Kowal的Q库:
(1) $q是跟Angular的$rootScope模型集成的,所以在Angular中,执行和拒绝都很快。
(2) $q promise是跟Angular模板引擎集成的,这意味着在视图中找到的任何promise都会在视图中被执行或者拒绝。
(3) $q很小,所以没有包含Q库的完整功能。

链式请求

then方法在初始promise被执行之后,返回一个新的派生promise。这种返回形式可以把另一个then接在初始的then方法结果之后。

 // 一个响应promise的服务 
 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;
});

在本例中,我们可以创建一个执行链,它允许我们中断基于更多功能的应用流程,可以籍此导向不同的结果。这个中断能让我们在执行链的任意时刻暂停或者推迟promise的执行。这个中断也是$http服务实现请求和响应拦截器的方式。
$q库自带了几个不同的有用方法。

all(promises)

  如果我们有多个promise,想要把它们合并成一个,可以使用$q.all(promises)方法来把它
们合并成一个promise。这个方法带有一个参数。

  • promises(数组或者promise对象):一个promise数组或者promisehash

all()方法返回单个promise,会执行一个数组或者一个散列的值。每个值会响应promise散列中的相同序号或者键。如果任意一个promise被拒绝了,结果的promise也会被拒绝。

defer()

defer()方法创建了一个deferred对象,它没有参数,返回deferred对象的一个实例。

reject(reason)

这个方法创建了一个promise,被以某一原因拒绝执行了。它专门用于让我们能在一个promise链中转发拒绝的promise,类似JS中的throw。在同样意义上,我们能在JS中捕获一个异常,也能够转发这个拒绝,我们需要把这个错误重新抛出。可以通过$q.reject(reason)来做到这点。
这个方法带有单个参数:

  • reason(常量、字符串、异常、对象):拒绝的原因。

reject()方法返回一个已经用某个原因拒绝的promise

when(value)

when()函数把一个可能是值或者能接着thenpromise包装成一个$q promise。这样我们就能处理一个可能是也可能不是promise的对象。
when()函数有一个参数:value,该参数是个值,或者是promise
when()函数返回了一个promise,我们可以像使用其他promise一样使用它。

0人点赞
"小礼物走一走,来简书关注我"


来自   https://www.jianshu.com/p/bd6b190837da

普通分类: