【问题标题】:Processing $http response in service在服务中处理 $http 响应
【发布时间】:2012-09-12 09:56:24
【问题描述】:

我最近在 SO 上发布了我在here 面临的问题的详细描述。由于我无法发送实际的$http 请求,我使用超时来模拟异步行为。在@Gloopy 的帮助下,从我的模型到视图的数据绑定工作正常

现在,当我使用$http 而不是$timeout(在本地测试)时,我可以看到异步请求成功并且data 在我的服务中填充了json 响应。但是,我的观点没有更新。

更新了 Plunkr here

【问题讨论】:

    标签: javascript angularjs angular-http


    【解决方案1】:

    我遇到了同样的问题,但是当我在网上冲浪时,我了解到 $http 默认返回一个承诺,然后我可以在返回“数据”后将它与“then”一起使用。看代码:

     app.service('myService', function($http) {
           this.getData = function(){
             var myResponseData = $http.get('test.json').then(function (response) {
                console.log(response);.
                return response.data;
              });
             return myResponseData;
    
           }
    });    
     app.controller('MainCtrl', function( myService, $scope) {
          // Call the getData and set the response "data" in your scope.  
          myService.getData.then(function(myReponseData) {
            $scope.data = myReponseData;
          });
     });
    

    【讨论】:

      【解决方案2】:

      请尝试以下代码

      可以拆分控制器(PageCtrl)和服务(dataService)

      'use strict';
      (function () {
          angular.module('myApp')
              .controller('pageContl', ['$scope', 'dataService', PageContl])
              .service('dataService', ['$q', '$http', DataService]);
          function DataService($q, $http){
              this.$q = $q;
              this.$http = $http;
              //... blob blob 
          }
          DataService.prototype = {
              getSearchData: function () {
                  var deferred = this.$q.defer(); //initiating promise
                  this.$http({
                      method: 'POST',//GET
                      url: 'test.json',
                      headers: { 'Content-Type': 'application/json' }
                  }).then(function(result) {
                      deferred.resolve(result.data);
                  },function (error) {
                      deferred.reject(error);
                  });
                  return deferred.promise;
              },
              getABCDATA: function () {
      
              }
          };
          function PageContl($scope, dataService) {
              this.$scope = $scope;
              this.dataService = dataService; //injecting service Dependency in ctrl
              this.pageData = {}; //or [];
          }
          PageContl.prototype = {
               searchData: function () {
                   var self = this; //we can't access 'this' of parent fn from callback or inner function, that's why assigning in temp variable
                   this.dataService.getSearchData().then(function (data) {
                       self.searchData = data;
                   });
               }
          }
      }());

      【讨论】:

        【解决方案3】:

        就在服务中缓存响应而言,这是另一个版本,似乎比我目前看到的更直接:

        App.factory('dataStorage', function($http) {
             var dataStorage;//storage for cache
        
             return (function() {
                 // if dataStorage exists returned cached version
                return dataStorage = dataStorage || $http({
              url: 'your.json',
              method: 'GET',
              cache: true
            }).then(function (response) {
        
                      console.log('if storage don\'t exist : ' + response);
        
                      return response;
                    });
        
            })();
        
        });
        

        此服务将返回缓存数据或$http.get

         dataStorage.then(function(data) {
             $scope.data = data;
         },function(e){
            console.log('err: ' + e);
         });
        

        【讨论】:

          【解决方案4】:

          让它变得简单。就这么简单

          1. 在您的服务中返回promise(无需在服务中使用then
          2. 在你的控制器中使用then

          演示。 http://plnkr.co/edit/cbdG5p?p=preview

          var app = angular.module('plunker', []);
          
          app.factory('myService', function($http) {
            return {
              async: function() {
                return $http.get('test.json');  //1. this returns promise
              }
            };
          });
          
          app.controller('MainCtrl', function( myService,$scope) {
            myService.async().then(function(d) { //2. so you can use .then()
              $scope.data = d;
            });
          });
          

          【讨论】:

          • 在你的链接中是app.factory,在你的代码中是app.service。在这种情况下应该是app.factory
          • app.service 也可以。另外-在我看来,这似乎是最优雅的解决方案。我错过了什么吗?
          • 似乎每次我遇到 Angular 问题时@allenhwkim 都有答案! (本周第三次 - 伟大的 ng-map 组件顺便说一句)
          • 我只想知道如何用 status_code 把成功和错误放在这里
          【解决方案5】:

          我真的不喜欢这样一个事实,因为“承诺”的做事方式,使用 $http 的服务的消费者必须“知道”如何解包响应。

          我只是想调用一些东西并取出数据,类似于旧的$scope.items = Data.getData(); 方式,即now deprecated

          我尝试了一段时间并没有想出一个完美的解决方案,但这是我最好的方法 (Plunker)。它可能对某人有用。

          app.factory('myService', function($http) {
            var _data;  // cache data rather than promise
            var myService = {};
          
            myService.getData = function(obj) { 
              if(!_data) {
                $http.get('test.json').then(function(result){
                  _data = result.data;
                  console.log(_data);  // prove that it executes once
                  angular.extend(obj, _data);
                }); 
              } else {  
                angular.extend(obj, _data);
              }
            };
          
            return myService;
          }); 
          

          然后是控制器:

          app.controller('MainCtrl', function( myService,$scope) {
            $scope.clearData = function() {
              $scope.data = Object.create(null);
            };
            $scope.getData = function() {
              $scope.clearData();  // also important: need to prepare input to getData as an object
              myService.getData($scope.data); // **important bit** pass in object you want to augment
            };
          });
          

          我已经发现的缺陷是

          • 您必须传入要添加数据的对象,这在 Angular 中不是直观或常见的模式
          • getData 只能接受对象形式的obj 参数(虽然它也可以接受数组),这对很多应用程序来说都不是问题,但它是一个痛苦的限制
          • 您必须准备输入对象$scope.data= {} 以使其成为对象(基本上是上面$scope.clearData() 所做的),或= [] 用于数组,否则它将不起作用(我们是已经不得不假设即将到来的数据)。我尝试在getData 中执行此准备步骤,但没有成功。

          尽管如此,它提供了一种模式来删除控制器“promise unwrap”样板,并且在您希望在多个地方使用从 $http 获得的某些数据同时保持其 DRY 的情况下可能很有用。

          【讨论】:

            【解决方案6】:

            这里有一个 Plunk 可以满足你的需求:http://plnkr.co/edit/TTlbSv?p=preview

            这个想法是您直接使用 Promise 及其“then”函数来操作和访问异步返回的响应。

            app.factory('myService', function($http) {
              var myService = {
                async: function() {
                  // $http returns a promise, which has a then function, which also returns a promise
                  var promise = $http.get('test.json').then(function (response) {
                    // The then function here is an opportunity to modify the response
                    console.log(response);
                    // The return value gets picked up by the then in the controller.
                    return response.data;
                  });
                  // Return the promise to the controller
                  return promise;
                }
              };
              return myService;
            });
            
            app.controller('MainCtrl', function( myService,$scope) {
              // Call the async method and then do stuff with what is returned inside our own then function
              myService.async().then(function(d) {
                $scope.data = d;
              });
            });
            

            这里有一个稍微复杂一点的版本,它缓存了请求,所以你只能在第一次完成它(http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview):

            app.factory('myService', function($http) {
              var promise;
              var myService = {
                async: function() {
                  if ( !promise ) {
                    // $http returns a promise, which has a then function, which also returns a promise
                    promise = $http.get('test.json').then(function (response) {
                      // The then function here is an opportunity to modify the response
                      console.log(response);
                      // The return value gets picked up by the then in the controller.
                      return response.data;
                    });
                  }
                  // Return the promise to the controller
                  return promise;
                }
              };
              return myService;
            });
            
            app.controller('MainCtrl', function( myService,$scope) {
              $scope.clearData = function() {
                $scope.data = {};
              };
              $scope.getData = function() {
                // Call the async method and then do stuff with what is returned inside our own then function
                myService.async().then(function(d) {
                  $scope.data = d;
                });
              };
            });
            

            【讨论】:

            • 有没有办法在服务用then拦截后仍然调用控制器中的成功和错误方法?
            • @PeteBD 如果我想从不同的控制器多次调用我的myService.async(),你将如何组织服务以便只有$http.get() 用于第一个请求,而所有后续请求只是返回在第一次调用myService.async() 时设置的本地对象数组。换句话说,我想避免对 JSON 服务的多次不必要的请求,而实际上我只需要发出一个。
            • @GFoley83 - 给你:plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview。如果您查看控制台,您会发现该请求只发出一次。
            • @PeteBD 我想你也可以直接在控制器中使用$scope.data = myService.async()
            • @Blowsie- 我已经更新了 Plunks。这里是原版(更新到 1.2RC3):plnkr.co/edit/3Nwxxk?p=preview 这里是一个使用服务:plnkr.co/edit/a993Mn?p=preview
            【解决方案7】:

            我认为更好的方法是这样的:

            服务:

            app.service('FruitsManager',function($q){
            
                function getAllFruits(){
                    var deferred = $q.defer();
            
                    ...
            
                    // somewhere here use: deferred.resolve(awesomeFruits);
            
                    ...
            
                    return deferred.promise;
                }
            
                return{
                    getAllFruits:getAllFruits
                }
            
            });
            

            在控制器中你可以简单地使用:

            $scope.fruits = FruitsManager.getAllFruits();
            

            Angular 会自动将解析后的awesomeFruits 放入$scope.fruits

            【讨论】:

            • deferred.resolve()?请更准确一点,$http 调用在哪里?另外,为什么要在 .service 中返回对象?
            【解决方案8】:

            我读过http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ [AngularJS 允许我们通过直接在作用域上放置一个 Promise 来简化我们的控制器逻辑,而不是在成功回调中手动处理解析的值。]

            如此简单方便:)

            var app = angular.module('myApp', []);
                        app.factory('Data', function($http,$q) {
                            return {
                                getData : function(){
                                    var deferred = $q.defer();
                                    var promise = $http.get('./largeLoad').success(function (response) {
                                        deferred.resolve(response);
                                    });
                                    // Return the promise to the controller
                                    return deferred.promise; 
                                }
                            }
                        });
                        app.controller('FetchCtrl',function($scope,Data){
                            $scope.items = Data.getData();
                        });
            

            希望有帮助

            【讨论】:

            • 不起作用。 defrred.promise 的返回值不是函数。
            • @PineappleUndertheSea 为什么需要一个函数?这是一个承诺对象。
            • @PineappleUndertheSea 你的意思是使用 deferred 而不是 defrred 吗?
            • 正如 PeteBD 指出的,这种形式 $scope.items = Data.getData(); is deprecated in Anglular
            【解决方案9】:

            与此相关,我遇到了类似的问题,但不是由 Angular 制作的 get 或 post,而是由第 3 方制作的扩展程序(在我的情况下为 Chrome 扩展程序)。
            我面临的问题是 Chrome 扩展程序不会返回 then() 所以我无法按照上述解决方案的方式执行此操作,但结果仍然是异步的。
            所以我的解决方案是创建一个服务并进行回调

            app.service('cookieInfoService', function() {
                this.getInfo = function(callback) {
                    var model = {};
                    chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
                        model.response= response;
                        callback(model);
                    });
                };
            });
            

            然后在我的控制器中

            app.controller("MyCtrl", function ($scope, cookieInfoService) {
                cookieInfoService.getInfo(function (info) {
                    console.log(info);
                });
            });
            

            希望这可以帮助其他人遇到同样的问题。

            【讨论】:

              【解决方案10】:

              tosh shimayama 有一个解决方案,但是如果你使用 $http 返回承诺并且承诺可以返回一个值的事实,你可以简化很多:

              app.factory('myService', function($http, $q) {
                myService.async = function() {
                  return $http.get('test.json')
                  .then(function (response) {
                    var data = reponse.data;
                    console.log(data);
                    return data;
                  });
                };
              
                return myService;
              });
              
              app.controller('MainCtrl', function( myService,$scope) {
                $scope.asyncData = myService.async();
                $scope.$watch('asyncData', function(asyncData) {
                  if(angular.isDefined(asyncData)) {
                    // Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
                  }
                });
              
              });
              

              coffeescript 中的小演示:http://plunker.no.de/edit/ksnErx?live=preview

              你的 plunker 用我的方法更新了:http://plnkr.co/edit/mwSZGK?p=preview

              【讨论】:

              • 我会按照你的方法进一步尝试。但是,我喜欢在服务中捕获结果而不是返回。请在此处查看与此相关的问题stackoverflow.com/questions/12504747/…。我喜欢在控制器中以不同的方式处理 $http 返回的数据。再次感谢您的帮助。
              • 你可以在服务中使用 Promise,如果你不喜欢 $watch 你可以做 ´promise.then(function(data){ service.data = data; }, onErrorCallback);`跨度>
              • 我添加了一个从你那里分叉出来的 plunker
              • 或者,您可以使用服务中的 $scope.$emit 和 ctrl 上的 $scope.$on 来告诉控制器数据已返回,但我真的没有看到任何好处
              【解决方案11】:

              因为是异步的,所以$scope是在ajax调用完成之前获取数据的。

              您可以在服务中使用$q 创建promise 并将其返回给 控制器,控制器在then()调用promise中获取结果。

              为您服务,

              app.factory('myService', function($http, $q) {
                var deffered = $q.defer();
                var data = [];  
                var myService = {};
              
                myService.async = function() {
                  $http.get('test.json')
                  .success(function (d) {
                    data = d;
                    console.log(d);
                    deffered.resolve();
                  });
                  return deffered.promise;
                };
                myService.data = function() { return data; };
              
                return myService;
              });
              

              然后,在您的控制器中:

              app.controller('MainCtrl', function( myService,$scope) {
                myService.async().then(function() {
                  $scope.data = myService.data();
                });
              });
              

              【讨论】:

              • +1 我最喜欢这个,因为它比其他的更面向对象。但是,您有什么理由不这样做 this.async = function() {this.getData = function() {return data} ?我希望你明白我的意思
              • @bicycle 我希望它以同样的方式,但它不会工作,因为承诺必须一直解决。如果您不这样做并尝试像往常一样访问它,则在访问内部数据时会出现引用错误。希望有意义吗?
              • 如果我理解正确,如果我想调用 myService.async() 两次或更多次,则有必要在 myService.async 中添加deffered = $q.defer()
              • 这个例子是经典的deferred anti-pattern。无需使用$q.defer 制造承诺,因为$http 服务已经返回了承诺。如果$http 返回错误,则返回的承诺将挂起。此外,.success.error 方法已被弃用,已被 removed from AngularJS 1.6.
              【解决方案12】:

              将 UI 绑定到数组时,您需要确保通过将长度设置为 0 并将数据推送到数组中来直接更新同一个数组。

              而不是这个(它设置了一个不同的数组引用 data 你的 UI 不会知道):

               myService.async = function() {
                  $http.get('test.json')
                  .success(function (d) {
                    data = d;
                  });
                };
              

              试试这个:

               myService.async = function() {
                  $http.get('test.json')
                  .success(function (d) {
                    data.length = 0;
                    for(var i = 0; i < d.length; i++){
                      data.push(d[i]);
                    }
                  });
                };
              

              Here is a fiddle 显示了设置新数组与清空和添加到现有数组之间的区别。我无法让你的 plnkr 工作,但希望这对你有用!

              【讨论】:

              • 那行不通。在控制台日志中,我可以看到 d 在成功回调中正确更新,但不是数据。可能是函数已经执行了。
              • 这个方法肯定可以工作,也许它与 d 不是数组的数据类型有关(例如,在 asp.net 中,您需要访问数组的 d.d)。有关在错误时将字符串推入数组的示例,请参见此 plnkr:plnkr.co/edit/7FuwlN?p=preview
              • angular.copy(d, data) 也可以。当向 copy() 方法提供目标时,它将首先删除目标的元素,然后从源中复制新的元素。
              猜你喜欢
              • 2016-12-23
              • 2011-01-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-02-08
              • 2021-09-17
              • 1970-01-01
              相关资源
              最近更新 更多