One challenge I encountered was that when making multiple nearly simultaneous calls to a service method that retrieves a list of project types using $resource, each call generated a new request instead of utilizing the same response/promise/data. After doing some research, I came across a solution which revealed an issue with creating a redundant $q.defer() called the deferred anti-pattern.
The initial code worked well as long as the calls were spaced out sufficiently. However, the consecutive calls did not share the projectTypes data. Additionally, any failed requests to retrieve project types would trigger dfr.reject(), which could be caught by .catch() in the controller.
angular.module('projects')
.factory('projectService', function(notificationService){
var shared = {};
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types'
},
...
});
var loadProjectTypes = function(){
var dfr = $q.defer();
if(shared.projectTypes){
dfr.resolve(shared.projectTypes);
}
else {
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
};
});
To optimize this code and eliminate unnecessary use of $q.defer(), I made some adjustments:
...
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types',
isArray: true,
transformResponse: function(response){
return JSON.parse(response).result.projectTypes;
}
},
...
});
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
...
Despite these improvements, there were still issues with error handling and promise chaining. In the original code, errors were appropriately handled in the catch statement of the controller. However, the updated version lacked similar error propagation. Attempting to replace dfr.reject() and dfr.resolve() with $q.reject() and $q.resolve() respectively proved ineffective.
This raised the question of whether the original implementation actually fell into the category of an anti-pattern or if it was a valid way of utilizing $q.defer(). As I delved deeper into seeking a solution, I realized the need for a consistent approach where the same promise/data was returned regardless of when the method was called. Ensuring proper error handling throughout the promise chain involving $resource posed a challenge yet to be resolved.
Continuing my search for a comprehensive solution, I outlined three main requirements:
Requirement 1: Enable multiple method calls while only triggering a single API request to update all callers with the same data.
Requirement 2: Allow usage of the method result as actual data per the promise specification. For example,
var myStuff = service.loadStuff()
should set myStuff
to "stuff".
Requirement 3: Facilitate promise chaining so that any errors within the chain can be managed by a single catch statement at the end. The goal is to have robust error handling across different chains and catches within the application.