在服务中处理$ http响应


233

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

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

在这里更新了Plunkr

Answers:


419

这是一个可以满足您需求的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;
    });
  };
});

13
服务拦截后,还有什么方法可以在控制器中仍然调用成功和错误方法then
andyczerwonka 2013年

2
@PeteBD如果我想myService.async()从各个控制器多次调用,如何组织服务,以便仅对$http.get()第一个请求执行,所有后续请求仅返回在第一次调用时设置的本地对象数组myService.async()。换句话说,当我实际上只需要发出一个请求时,我想避免对JSON服务进行多个不必要的请求。
GFoley83

5
@ GFoley83-到这里去了:plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview。如果您查看控制台,您会看到该请求仅发出一次。
皮特·BD

3
@PeteBD我想您也可以$scope.data = myService.async()直接在控制器中使用。
朱利安

2
@ Blowsie-我已经更新了Plunks。这是原始文件(已更新为1.2RC3):plnkr.co/edit/3Nwxxk?p=preview这是一项使用服务的文件:plnkr.co/edit/a993Mn?p=preview
Pete BD

82

简单一点。就这么简单

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

演示 http://plnkr.co/edit/cbdG5p?p=预览

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.serviceapp.factory在这种情况下应该是这样。
重验证码2014年

1
app.service也可以。另外-对我来说,这似乎是最优雅的解决方案。我想念什么吗?
user1679130

1
似乎每次遇到Angular问题时,@ allenhwkim都有答案!(本周第三次-伟大的ng-map组件顺便说一句)
Yarin

我只想知道如何使用status_code在这里放置成功和错误
Anuj

58

因为它是异步的,$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();
  });
});

2
+1我最喜欢这一个,因为它比其他东西更具有面向对象。但是没有任何理由,你不这样做this.async = function() {,并this.getData = function() {return data}?希望您能理解我的意思
骑自行车

@bicycle我以同样的方式想要它,但是它无法正常工作,因为必须完全解决承诺。如果您不按常规尝试访问它,则在访问内部数据时会出现参考错误。希望有道理吗?
user6123723 2013年

如果我理解正确,那么deffered = $q.defer()如果我想两次或更多次调用myService.async(),则必须在myService.async中添加
demas 2015年

1
此示例是经典的递延反模式$q.defer由于$http服务已经返回了承诺,因此无需制造承诺。如果$http返回错误,则返回的承诺将挂起。此外,不赞成.success.error方法,并已从AngularJS 1.6中将其删除
georgeawg '16

23

tosh shimayama有解决方案,但是如果您使用$ http返回promise并且promise可以返回值的事实,则可以简化很多工作:

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

您的窃听器已通过我的方法进行了更新:http ://plnkr.co/edit/mwSZGK?p=preview


我将进一步尝试您的方法。但是,我喜欢在服务中捕获结果而不是返回。在此处stackoverflow.com/questions/12504747/…看到与此相关的问题。我喜欢在控制器中以不同方式处理$ http返回的数据。再次感谢您的帮助。
2012年

您可以在服务中使用Promise,如果您不喜欢$ watch,则可以执行‘promise.then(function(data){service.data = data;},onErrorCallback);'
Guillaume86

我添加了一个从您的叉车中抽出的矮子
Guillaume86

1
或者,您可以使用服务中的$ scope。$ emit和ctrl上的$ scope。$ on告诉您控制器数据已返回,但我并没有真正看到收益
Guillaume86 2012年

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


4
deferred.resolve()?请更精确一点,$ http呼叫在哪里?另外,为什么还要在.service中返回对象?

6

我遇到了同样的问题,但是当我在互联网上冲浪时,我了解到$ 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;
      });
 });

4

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

这是一个小提琴,显示了设置新数组与清空与添加到现有数组之间的区别。我无法让您的plnkr工作,但希望这对您有用!


那没有用。在控制台日志中,我可以看到成功回调中的d已正确更新,但数据未更新。该函数可能已经执行。
2012年

此方法肯定可以工作,也许它与d的数据类型不是数组有关(例如,在asp.net中,您需要访问dd的数组)。请参阅以下plnkr示例,以了解在错误时将字符串推入数组的示例:plnkr.co/edit/7FuwlN?p=preview
Gloopy 2012年

1
angular.copy(d, data)也可以。当将目标提供给copy()方法时,它将首先删除目标的元素,然后从源中复制新的元素。
Mark Rajcok

4

与此相关的是,我遇到了类似的问题,但是不是通过Angular进行的get或post而是通过第三方进行的扩展(在我的情况下为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);
    });
});

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


4

我已经阅读了http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ [AngularJS允许我们通过将诺言直接放在范围上来简化控制器逻辑,而不是手动将已解决的问题成功回调中的值。]

很简单方便:)

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为什么需要一个函数?这是一个承诺对象。
Chev

@PineappleUndertheSea您是要使用延期还是不延期?
德里克2014年

2
作为PeteBD指出,这种形式$scope.items = Data.getData(); 被弃用Anglular
poshest

2

我真的不喜欢这样的事实,由于“承诺”的处理方式,使用$ http的服务的使用者必须“知道”如何解压缩响应。

我只想调用一些东西并取出数据,类似于现在不推荐使用的旧$scope.items = Data.getData();方法。

我尝试了一段时间,但没有提出完美的解决方案,但这是我的最佳拍档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()上面的一样),或者= []为一个数组,否则将无法工作(我们已经不得不承担一些关于什么样的数据来了)。我试图在IN进行此准备步骤getData,但是没有运气。

但是,它提供了一种删除控制器“承诺解开”样板的模式,在希望将从$ http获得的某些数据在多个位置使用而保持其DRY的情况下,该模式很有用。


1

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

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

0

请尝试以下代码

您可以拆分控制器(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;
             });
         }
    }
}());

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.