在服务中处理 $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=previewangular.copy(d, data)
也可以。当向 copy() 方法提供目标时,它将首先删除目标的元素,然后从源中复制新的元素。以上是关于在服务中处理 $http 响应的主要内容,如果未能解决你的问题,请参考以下文章