Javascript 在承诺链中使用$resource(修复延迟反模式)
我有一个服务,其方法使用$resource获取项目类型列表。它对我来说工作得很好,除了如果我进行多个几乎同时进行的调用(比如说,两个指令),每个调用将创建另一个请求,而不是使用相同的响应/$promise/数据 我找到了引导我去和TL;DR,显然它创建了一个冗余的$q.defer(),实际上被认为是一个 如果获取项目类型的调用明显错开(比如间隔超过毫秒),那么下面的代码工作得很好。连续调用使用shared.projectTypes解析。如果获取项目类型的请求失败,则调用控制器中的Javascript 在承诺链中使用$resource(修复延迟反模式),javascript,angularjs,promise,angular-resource,Javascript,Angularjs,Promise,Angular Resource,我有一个服务,其方法使用$resource获取项目类型列表。它对我来说工作得很好,除了如果我进行多个几乎同时进行的调用(比如说,两个指令),每个调用将创建另一个请求,而不是使用相同的响应/$promise/数据 我找到了引导我去和TL;DR,显然它创建了一个冗余的$q.defer(),实际上被认为是一个 如果获取项目类型的调用明显错开(比如间隔超过毫秒),那么下面的代码工作得很好。连续调用使用shared.projectTypes解析。如果获取项目类型的请求失败,则调用控制器中的.catch将触
.catch
将触发dfr.reject()
,并将其捕获
angular.module('projects')
.factory('projectService', function(notificationService){
// an object to share data gathered by this service
var shared = {};
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types'
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
var dfr = $q.defer();
// if we've already done this, just return what we have.
if(shared.projectTypes){
dfr.resolve(shared.projectTypes);
}
else {
// begin anti-pattern (?)
projectResource.getProjectTypes(null,
function(response){
shared.projectTypes = response.result.projectTypes;
dfr.resolve(response);
},
function(errResponse){
console.error(errResponse);
notificationService.setNotification('error', errResponse.data.messages[0]);
dfr.reject(errResponse);
});
}
return dfr.promise;
};
return {
shared: shared,
project: projectResource,
loadProjectTypes: loadProjectTypes
};
});
因此,我读到没有必要使用这个额外的var dfr=$q.defer()
,因为$resource将为我提供所有这些。经过一点重构,我最终得出以下结论:
...
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types',
isArray: true,
transformResponse: function(response){
return JSON.parse(response).result.projectTypes;
}
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
...
为了澄清这一点,我在资源中添加了isArray
和transformResponse
,因为我的API返回了大量额外的元信息,而我想要的只是一个类型数组。在我的loadProjectTypes
方法中,我使用了与最初相同的缓存,但我缓存的是projectResource.getProjectTypes()
的结果,而不是实际的响应数据(尽管由于transformResponse,这可能正是我缓存的结果)
这是一条很好的途径(减少对API的调用,向每个人返回相同的东西等等),但我的主要问题是错误的链接和捕获
在我最初的反模式示例中,如果GET/project/types出现错误,我将使用dfr.reject()
,然后将其传递回我的控制器,其中有一个.catch()
这是来自控制器的代码,该控制器实际发出获取项目类型的原始请求:
$q.all([
projectService.loadProjects(),
userService.loadUserRole('project_manager'),
userService.loadUserRole('sales_representative'),
projectService.loadProjectTypes(),
clientService.loadClients()
])
.then(function(response){
// doing stuff with response
})
.catch(function(errResponse){
// expecting errors from service to bubble through here
console.error(errResponse);
});
在反模式示例中,dfr.reject
导致错误显示在catch中,但在我假定的非反模式示例中,它没有发生。我不知道如何以与以前相同的方式拒绝或解决$resource结果。如果承诺链接的一个要点是有一个点来处理来自任何链接的错误,那么我做得对
我尝试使用$q.resolve()/reject(),因为我不再有dfr了,但这看起来很愚蠢,而且无论如何都不起作用
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes(null,
function(response){
return $q.resolve(response);
},
function(errResponse){
return $q.reject(errResponse);
}));
如何使链工作,以便控制器中的.catch()是处理错误的地方
我是否真的在我的原始代码中实现了反模式,或者这是使用$q.defer()的公认方法之一,而它根本不是反模式
在这本书中,有一个答案是:
“这有什么问题吗?但是这个图案很有效!你真幸运。”。
不幸的是,它可能没有,因为你可能忘记了一些优势
在我所看到的一半以上的事件中,作者
忘记处理错误处理程序。“
然而,我的原始代码正在解决这些错误。除了每个打电话的人都得到了自己的承诺之外,这一切都在起作用。我觉得我错过了一些东西
我可能会感到困惑,但我认为loadProjectTypes
方法应该向调用它的任何人返回相同的承诺/数据,无论何时调用它。它应该是所有项目类型的真正来源,并且只在第一次调用时调用一次
每当我查找这些内容时(这些主题上有很多紫色/访问过的谷歌链接),每个人都会用人为的例子显示链接,或者只使用$http或其他东西。我还没有发现任何人在使用$resource的承诺链中执行错误捕获
更新:为解决方案添加我的需求。我把它们贴在了我的答案里,但我也想把它们也包括在原来的帖子里
要求1:允许对方法进行多个调用,但只发出一个API请求,用相同的数据更新所有调用方
要求2:必须能够使用方法的结果作为实际数据,正如承诺规范的意图var myStuff=service.loadStuff()
实际上应该将myStuff
设置为“stuff”
要求3:必须允许promise链接,以便链的任何部分中的所有错误都可以被链末端的单个捕捉器捕捉。正如我在解决方案中发现的,可以有多条链和多个捕获,但关键是每条链都有一个捕获,链中任何断开的“链接”都应该将其错误报告给各自的捕获。我不久前写了一篇与此非常类似的文章,但有几个关键区别:
module.factory("templateService", function ($templateCache, $q, $http) {
var requests = {};
return {
getTemplate: function getTemplate(key, url) {
var data = $templateCache.get(key);
// if data already in cache, create a promise to deliver the data
if (data) {
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve({ data: data });
return promise;
}
// else if there is an open request for the resource, return the existing promise
else if (requests[url]) {
return requests[url];
}
// else initiate a new request
else {
var req = $http.get(url);
requests[url] = req;
req.success(function (data) {
delete requests[url];
$templateCache.put(key, data);
});
return req;
}
},
};
});
这不是一直都是这样吗,只要你一说出你的问题,你就会找到你的解决方案 要求1:每个方法调用只发出一个请求。这可以通过反模式的原始修复来解决。这将始终通过返回缓存的$resource或同时返回和缓存来返回$resource结果
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
要求2:能够使用服务方法作为承诺,在这里我可以将$scope变量的值直接设置为loadProjectTypes()的结果。使用上面修改过的方法,我可以简单地声明$scope.theTypes=projectService.loadProjectTypes()
,当类型出现时,它将自动填充类型列表,就像promise规范所希望的那样
要求3:能够将多个$resource调用链接在一起,并让它们的错误被单个.catch()
捕获。通过使用re的$promise
$q.all([
...,
projectService.loadProjectTypes().$promise,
...
])
.then(function(response){
// my project types comes in as response[n]
})
.catch(function(errResponse){
// but any errors will be caught here
});
angular.module('projects')
.factory('projectService', function(notificationService){
// an object to share data gathered by this service
var shared = {};
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types',
isArray: true,
transformResponse: function(response){
return JSON.parse(response).result.projectTypes;
}
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
return {
shared: shared,
project: projectResource,
loadProjectTypes: loadProjectTypes
};
});
angular.module('projects')
.directive('projectPageHeader', ['projectService', function(projectService){
return {
restrict: 'E',
scope: {
active: '@',
},
templateUrl: 'src/js/apps/projects/partials/dir_projectPageHeader.html',
replace: true,
controller: function($scope){
$scope.projectService = projectService;
// sets the types to the array of types
// as given by the transformResponse
$scope.types = projectService.getProjectTypes();
// could also do a .$promise.catch here if I wanted.
// all catches will fire if get projectTypes fails.
}
};
}]);
angular.module('projects')
.controller('projectListPageController', [
'$scope','projectService',
function($scope, projectService){
// load it all up
$q.all([
projectService.loadProjectDetails($routeParams.projectId).$promise,
userService.loadUserRole('project_manager').$promise,
userService.loadUserRole('sales_representative').$promise,
projectService.loadProjectStatuses().$promise,
projectService.loadProjectTypes().$promise,
clientService.loadClients().$promise
])
.then(function(response){
// do work with any/all the responses
})
.catch(function(errResponse){
// catches any errors from any of the $promises above.
})
}]);
// This code is just placed in your controllers init section
loadProjectTypes()
.$promise
.then(function(response){
// ... do something with response (or noop)
})
.catch(function(errResponse){
// ... do something with error
});
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
// again, this code is just placed somewhere near the top of your controller
$q.all([
loadProjectTypes().$promise
])
.then(...)
.catch(...);