如何让控制器等待从角度服务解决的承诺
Posted
技术标签:
【中文标题】如何让控制器等待从角度服务解决的承诺【英文标题】:How to make controller wait for promise to resolve from angular service 【发布时间】:2014-11-12 00:42:45 【问题描述】:我有一个服务正在向后端发出 AJAX 请求
服务:
function GetCompaniesService(options)
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url);
控制器:
var CompaniesOb = new GetCompanies();
CompaniesOb.CompaniesPromise.then(function(data)
$scope.Companies = data;
);
我希望我的服务处理“.then”函数,而不是必须在我的控制器中处理它,并且我希望能够让我的控制器在服务内部的承诺之后对来自服务的数据采取行动已解决。
基本上,我希望能够像这样访问数据:
var CompaniesOb = new GetCompanies();
$scope.Companies = CompaniesOb.Companies;
在服务本身内部处理承诺的解决方案。
这可能吗?还是我可以从服务外部访问该承诺的解决方案的唯一方法?
【问题讨论】:
【参考方案1】:如果您只想在服务本身中处理$http
的响应,则可以将then
函数添加到服务中,然后从then
函数中进行更多处理然后return
,就像这样:
function GetCompaniesService(options)
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url).then(function(response)
/* handle response then */
return response
)
但您仍然可以在控制器中使用 promise,但您返回的内容已经在服务中处理。
var CompaniesOb = new GetCompanies();
CompaniesOb.CompaniesPromise.then(function(dataAlreadyHandledInService)
$scope.Companies = dataAlreadyHandledInService;
);
【讨论】:
对不起,我看不出这和我原来的帖子有什么不同,只是语法有点不同。 不,这不仅仅是一种不同的语法。服务中的then
函数将在控制器中的then
函数之前执行,让您有机会在控制器访问响应之前处理它。
是的,但不幸的是我仍然必须通过承诺中的控制器访问它。这就是我试图避免的。这是解决方案的一半。
是的,您必须通过控制器中的 promise 语法来访问它。 Angular 模板用于自动解包 Promise,您可以在其中执行您说您喜欢的语法。但是他们已经弃用然后完全删除了该功能。现在你必须像我的例子一样自己解开承诺。
我将保持开放状态,看看是否有人有其他想法。如果不是,我想这就是答案!【参考方案2】:
做到这一点没有问题!
您必须记住的主要事情是,您必须在服务中保持相同的对象引用(在 javascript 数组中是对象)。
这是我们的简单 html:
<div ng-controller = "companiesCtrl">
<ul ng-repeat="company in companies">
<li>company</li>
</ul>
</div>
这是我们的服务实现:
serviceDataCaching.service('companiesSrv', ['$timeout', function($timeout)
var self = this;
var httpResult = [
'company 1',
'company 2',
'company 3'
];
this.companies = ['preloaded company'];
this.getCompanies = function()
// we simulate an async operation
return $timeout(function()
// keep the array object reference!!
self.companies.splice(0, self.companies.length);
// if you use the following code:
// self.companies = [];
// the controller will loose the reference to the array object as we are creating an new one
// as a result it will no longer get the changes made here!
for(var i=0; i< httpResult.length; i++)
self.companies.push(httpResult[i]);
return self.companies;
, 3000);
]);
最后是你想要的控制器:
serviceDataCaching.controller('companiesCtrl', function ($scope, companiesSrv)
$scope.companies = companiesSrv.companies;
companiesSrv.getCompanies();
);
说明
如上所述,诀窍是保持服务和控制器之间的引用。一旦尊重这一点,您就可以将控制器范围完全绑定到服务的公共属性上。
Here a fiddle that wraps it up.
在代码的 cmets 中,您可以尝试取消注释不起作用的部分,您将看到控制器如何丢失引用。事实上,控制器将继续引用旧数组,而服务将更改新数组。
最后一件重要的事情:请记住,$timeout 会触发 rootSCope 上的 $apply()。这就是我们的控制器作用域“单独”刷新的原因。没有它,如果你尝试用普通的 setTimeout() 替换它,你会看到控制器没有更新公司列表。 要解决此问题,您可以:
如果您的数据是使用 $http 获取的,则不要执行任何操作,因为它会在成功时调用 $apply 换行会导致 $timeout(..., 0); 在服务中注入 $rootSCope 并在异步操作完成时对其调用 $apply() 在控制器中添加 $scope.$apply() 到 getCompanies() 承诺成功希望这会有所帮助!
【讨论】:
见Why are angular $http success/error methods deprecated? Removed from v1.6?。 另见What is the explicit promise construction antipattern and how do I avoid it?。 答案使用了一种不必要的复杂、容易出错并被视为anti-pattern 的技术。没有必要用 $q.defer 构造一个 promise,因为 $timeout 和 $http 服务都已经返回了 promise。 确实,然后我会编辑答案。虽然这里的目标不是展示承诺模式,而是公开一个集合 getter 并在服务中保存引用,以便让使用这个集合的组件“感知变化”。【参考方案3】:您可以将 $scope 传递给 GetCompanies 并将 $scope.Companies 设置为服务中的数据
function GetCompaniesService(options,scope)
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url).then(function(res)
scope.Companies = res;
);
您必须注意随后使用数据的顺序。这就是一开始就做出承诺的原因。
【讨论】:
即使这是可能的,也被认为是不好的做法。我不明白为什么 Axschech 不喜欢原始方法 @ABOS 的想法是将数据(及其状态)保存在服务中,而不是控制器中。最初的例子和这个例子都相反。将数据分配给控制器的作用域会将状态置于控制器手中,而不是服务。 我现在明白你的意思了。但是,为了共享数据而保持数据服务的目的是什么?即使是这样,我也不会这样做。我宁愿将数据存储在控制器上,如果我确实想共享数据/将数据放在服务上,我只需调用服务的 setSharedData(sharedData) 方法。这使得服务代码更具可测试性和可重用性 @ABOS 我确实理解这个原则,但其想法是保持数据的状态(从服务器拉出后将更改客户端)。我只是觉得继续引用服务的版本比使用控制器对象更容易。否则,该服务只是 $http.get() 的外壳以上是关于如何让控制器等待从角度服务解决的承诺的主要内容,如果未能解决你的问题,请参考以下文章
Javascript - 异步等待和获取 - 返回值,而不是承诺?
需要帮助在 NodeJS 中使用 Sinon 存根异步 / 等待承诺