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

时间:2015-05-21 13:59:56

标签: angularjs asynchronous promise angularjs-service angular-promise

在我的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.getItem()完成加载SomeService之前调用somefile.json,则SomeService将无法返回任何数据。

在实践中,如果我加载应用程序几次,一些加载将起作用(SomeService 首先加载somefile.json ,控制器将根据需要显示数据,而其他负载不会( ie SomeController将尝试在数据实际到之前从SomeService检索数据加载,一切都会崩溃和燃烧。)

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

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

怀疑 Angular's $q service在这里有用。但是,我是承诺的新手,而且我不确定我应该在这里使用$q(甚至我是不是正在吠叫正确的树)。

你能把我推向正确的方向吗?我非常感激。

5 个答案:

答案 0 :(得分:2)

我建议更好地利用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'
      });
  }]);

这样,您可以让角度负责解决此依赖关系作为路由过程的一部分。

缓存设置为true也会缓存它,因此您不会两次执行相同的Get请求,而Angular只会在解析依赖关系时显示您的视图。

因此,在您的应用中, SomeController 与路由过程中的视图配对,请使用解决来解析,并将其注入控制器。

答案 1 :(得分:2)

试试这段代码

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;
});

答案 2 :(得分:1)

以下是我在自己项目中的表现。

您的服务

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

} );

重点:承诺

在我看来,处理异步调用的责任归于控制器。我随时都会返回$ http promiss。

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

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

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

如果您有更多问题可以随意提问。我希望它对你有帮助。

答案 3 :(得分:0)

是你可以选择的两个不错的选择

  1. 使用回调

  2. 使用$ q return promise

  3. 使用回调:

    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
    });
    

答案 4 :(得分:0)

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

我在我的服务中使用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
         });