如何构造一个 Angular 服务以便它可以处理异步调用?

How to structure an Angular service so it can handle asynchronous calls?

在我的 Angular 应用程序中,我有两个控制器,它们都需要访问相同的数据。

为此,我创建了一个服务,负责保存和提供对该数据的访问:

angular.module("SomeModule").factory( "SomeService", function( $http ) {

    var svc = {};
    var data = {};

    // on initialization, load data from the server
    $http.get( "somefile.json" )
        .success( function( data ) {
            svc.data = data;
        } );

    svc.getItem = function( id ) {
        // find the specified item within svc.data, and return its value
    };

    return svc;

} );

...我已经将该服务注入到两个控制器中的每一个中:

angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {

    var ctrl = this;

    ctrl.item = null; // set an initial value

    // load the item that was requested in the URL
    ctrl.item = SomeService.getItem( $routeParams.id );

} );

这个几乎 有效——但它有一个很大的缺陷。如果 SomeControllerSomeService 完成加载 somefile.json 之前调用 SomeService.getItem(),则 SomeService 将没有任何数据到 return。

实际上,如果我多次加载该应用程序,一些加载将起作用(SomeService 首先完成加载 somefile.json,控制器将根据需要呈现数据),其他加载则不会(SomeController 将尝试从中检索数据SomeService在数据真正加载之前,一切都会崩溃和烧毁。

显然,我需要找到一些方法来推迟 getItem() 的执行,直到 SomeService 实际上准备好处理这些调用。但我不确定最好的方法。

我能想到一些相当复杂的解决方案,例如在 SomeService 中构建我自己的调用队列,并连接一堆复杂的回调。但是必须有一个更优雅的解决方案。

怀疑 Angular's $q service 在这里可能有用。但是,我是 promises 的新手,我不确定我应该如何在这里使用 $q(甚至我是否在咆哮正确的树)。

你能把我推向正确的方向吗?我将不胜感激。

您还有 2 个不错的选择

  1. 使用回调

  2. 使用 $q return 承诺

使用回调:

svc.getItem = function( id,callback ) {
      $http.get( "somefile.json" )
        .success( function( data ) {
            svc.data = data;
            callback(svc.data)
        } );
    };

在控制器中

SomeService.getItem( $routeParams.id,function(data){
 ctrl.item =  data
 } );

使用承诺:

  svc.getItem = function( id) {

   var deferred = $q.defer();

      $http.get( "somefile.json" )
        .success( function( data ) {
            svc.data = data;
            deferred.resolve(svc.data);
        } )
        .error(function (error) {
        deferred.reject(error);
         });

      return deferred.promise;
     ;
    };

在控制器中

SomeService.getItem( $routeParams.id).then(function (data) {
     ctrl.item =  data
},
function (error) {
    //do something with error
});

我们是这样做的,我们使用 $q 来延迟异步调用将提供您的服务的任何内容,然后我获取响应的数据部分并解析它,它将所需的数据发送到控制器(没有状态, headers...).

我在我的服务中使用 try catch 语句,以使错误处理远离控制器。

angular.module("JobsService", [])
    .factory("JobsService", ['$q', '$http', '$log', function ($q, $http, $log) {

        var serviceName = "JobsService";
        var url = "http://localhost:8080/path/to/resource/";

        var service = {};

        service.getAll = function () {

            var deferred = $q.defer();

            try {
                $http.get(url + "/jobs")
                        .success(function (response, status) {
                            $log.debug("GET response in " + serviceName + " returned with status " + status);
                            deferred.resolve(response);
                        })
                        .error(function (error, status) {
                            deferred.reject(error + " : " + status);
                        });
            } catch (err) {
                $log.error(err);
                deferred.reject();
            }

            return deferred.promise;
        };

        return service;
    }]);

然后在控制器中

JobsService.getAll()
         .then(function (response) {

             $scope.jobs = response;
             // records are stored in $scope.jobs
         }, function (response) {
             $scope.jobs = undefined;
         })
             .finally(function () {
              // will always run
         });

这是我在自己的项目中的做法。

您的服务

angular.module("SomeModule").factory( "SomeService", function( $http ) {

    var svc = {};
    svc.data = {};

    // on initialization, load data from the server
    svc.getData = function(){
        return $http.get( "somefile.json" );
    };

    return svc;

} );

您的控制器

angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {

    ctrl.items = null; // set an initial value

    // load the item that was requested in the URL
    SomeService.getData().success(function(data){
        ctrl.items = data;
    }).error(function(response){
        console.err("damn");
    });

} );

要点:承诺

依我愚见,处理异步调用的责任在于控制器。只要有可能,我总是 return $http 承诺。

 svc.getData = function(){
    return $http.get( "somefile.json" );
 };

您可以在服务中添加一些逻辑,但您始终必须 return 承诺。 (要知道:.success() 承诺 return 承诺)

控制器将具有根据异步调用的响应知道如何行为的逻辑。他必须知道在成功和错误的情况下如何表现。

如果您有更多问题,请随时提问。希望对你有帮助。

我建议更好地利用 AngularJS 的路由功能,它允许您解决依赖关系,以及 $http 服务缓存,并相应地构建您的应用程序。

因此,我认为您需要完全摆脱您的服务。

从下面的例子开始,taken straight from the Angular documentation:

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

因此 PhoneListCtrl 和 PhoneDetailCtrl 都需要来自 somefile.json 的数据。我会像这样将数据注入每个控制器:

(function(){
        angular.module('phonecatApp').controller('PhoneListCtrl', ['somefileJsonData', function(somefileJsonData){
            this.someFileJsonData = someFileJsonData;
        }]);
})();

PhoneDetailCtrl 相同的想法。

然后像这样更新您的路由:

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl',
        resolve:{ somefileJsonData: ['$http',function($http){
            return $http.get("somefile.json", { cache: true });
        }] }
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl',
        //same resolve
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

这样,您就让 angular 在路由过程中负责解决此依赖关系。

cache 设置为 true 也会对其进行缓存,这样您就不会两次执行相同的 Get 请求,并且 Angular 只会在依赖项为已解决。

因此,在您的应用中,SomeController 与视图配对作为路由过程的一部分,使用 resolve 来解析 item,并将其注入控制器。

试试这个代码

angular.module("SomeModule").factory("SomeService", function ($http) {
    var svc = {};

    svc.getList = function () {
       return $http.get("somefile.json");
    };

    svc.getItem = function (id) {
        svc.getList().then(function (response) {
            // find the specified item within response, and return its value
        });
    };
    return svc;
});