在服务中处理 $http 响应

Posted

技术标签:

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

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

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

更新了 Plunkr here

【问题讨论】:

【参考方案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】:

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

    在您的服务中返回promise(无需在服务中使用then) 在你的控制器中使用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

【讨论】:

我会按照你的方法进一步尝试。但是,我喜欢在服务中捕获结果而不是返回。请在此处查看与此相关的问题***.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() 方法提供目标时,它将首先删除目标的元素,然后从源中复制新的元素。

以上是关于在服务中处理 $http 响应的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中处理 Http 304 响应

怎样用JAVA实现模拟HTTP请求,得到服务器的响应时间等参数

HTTP请求在Golang中写入响应后中止

node.js中HTTP请求与响应处理

Node.js之HTTP请求与响应

请求响应原理以及HTTP协议