服务应该暴露他们的异步性吗?

Mar*_*cok 18 angularjs angularjs-service

我正在编写一个将异步检索数据的服务($ http或$ resource).我可以通过返回一个最初为空的数组来隐藏它是异步的这一事实,但最终会被填充:

.factory('NewsfeedService1', ['$http', function($http) {
   var posts = [];
   var server_queried = false;
   return {
      posts: function() {
         if(!server_queried) {
            $http.get('json1.txt').success(
              function(data) {
                server_queried = true;
                angular.copy(data, posts);
            });
         }
         return posts;
      }
   };
}])
.controller('Ctrl1', ['$scope','NewsfeedService1',
function($scope, NewsfeedService1) {
    $scope.posts = NewsfeedService1.posts();
}])
Run Code Online (Sandbox Code Playgroud)

或者我可以通过回复承诺来暴露异步性:

.factory('NewsfeedService2', ['$http', function($http) {
  var posts = [];
  var server_queried = false;
  var promise;
  return {
     posts_async: function() {
       if(!promise || !server_queried) {
         promise = $http.get('json2.txt').then(
           function(response) {
              server_queried = true;
              posts = response.data;
              return posts;
         });
       }
       return promise;
     }
  };
}])

.controller('Ctrl2', ['$scope','NewsfeedService2',
function($scope, NewsfeedService2) {
  NewsfeedService2.posts_async().then(
    function(posts) {
      $scope.posts = posts;
  });
  // or take advantage of the fact that $q promises are
  // recognized by Angular's templating engine:
  // (note that Peter and Pawel's AngularJS book recommends against this, p. 100)
  $scope.posts2 = NewsfeedService2.posts_async();
}]);
Run Code Online (Sandbox Code Playgroud)

(Plunker - 如果有人想要使用上述两种实现方式.)

暴露异步性的一个潜在优势是我可以通过向then()方法添加错误处理程序来处理控制器中的错误.但是,我可能会在应用程序范围的拦截器中捕获并处理$ http错误.

那么,何时应该公开服务的异步性?

Mic*_*ley 9

我的猜测是你会在这个围栏的两边找到人.就个人而言,我觉得你应该总是暴露出图书馆或函数的异步性(或者更正确:我觉得你永远不应该隐藏图书馆或函数的异步性).主要原因是透明度; 例如,这会有用吗?

app.controller('MyController', function(NewsfeedService) {
  $scope.posts = NewsfeedService.posts();
  doSomethingWithPosts($scope.posts); // <-- will this work?
});
Run Code Online (Sandbox Code Playgroud)

如果您正在使用第一种方法(例如$resource),它将不会,即使$scope.posts在技​​术上是一个数组.如果doSomethingWithPosts有自己的异步操作,最终可能会遇到竞争条件.相反,您必须使用异步代码:

app.controller('MyController', function(NewsfeedService) {
  $scope.posts = NewsfeedService.posts(function() {
    doSomethingWithPosts($scope.posts);
  });
});
Run Code Online (Sandbox Code Playgroud)

(当然,你可以让回调接受posts作为一个参数,但我仍然认为这是令人困惑和非标准的.)

幸运的是,我们有承诺,承诺的目的是代表一项行动的未来价值.此外,由于使用Angular $q库创建的promise 可以绑定到视图,因此没有任何问题:

app.controller('MyController', function(NewsfeedService) {
  $scope.posts = NewsfeedService.posts();
  // $scope.posts is a promise, but when it resolves
  // the AngularJS view will work as intended.
});
Run Code Online (Sandbox Code Playgroud)

[更新:你不能再将promises直接绑定到视图; 您必须等待解析承诺并手动分配范围属性.]

另外,Restangular是一种流行的替代品$resource,使用promises,而AngularJS自己$resource将在1.2中支持它们(他们可能已经在最新的1.1.x中支持它们).

  • 我想补充说,当服务器响应也是一个空数组时,空数组会产生歧义. (4认同)

Cha*_*ani 5

我总是选择异步选项,因为我不喜欢隐藏底层框架的异步性质.

同步版本在使用时可能看起来更干净,但它无意中导致了开发人员没有意识到调用本质上是异步并且在打电话后尝试访问数据的错误.

SO充满了人们犯这个错误的问题,$resource考虑到它本质上是同步的,并期待回应.$resource也采用与选项1类似的方法,其中结果在调用完成后填充,但仍然$resource暴露成功和失败函数.

如果返回promises,AngularJS会尝试隐藏异步调用的复杂性,因此直接绑定到promise就像是在进行同步调用.


Cla*_*Pan 5

我说不,因为这使得使用这种方式构建的多个服务变得更加困难.使用promises,您可以使用$q.all()多个请求并在所有请求完成时做出响应,或者您可以通过传递承诺将操作链接在一起.

对于同步样式服务,没有直观的方法可以做到这一点.