Caching a promise object in AngularJS service
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data
variable and the resolution with undefined
is not how promises are intended to work. Instead, fulfill the promise with the result data
! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data })
it is simply getData().then(function(data) { … })
.
To further improve the pattern, you might want to hide dataPromise
in a closure scope, and notice that you will need a lookup for different promises when getData
takes a parameter (like the url).
Angular: using promise in service to store data once, cache
With promises you can chain then
calls.
app.factory('Team', function($http) {
return {
promise: $http.get('/api/teams').then(function (response) {
return response.data;
})
}
});
This notation is not only shorter, but failures($q.reject) are also propagated.
AngularJS Bypass promise if data is present in cache
What you should be doing is always returning a promise. In this case, you can simply return an already resolved promise. You can read more about this here.
if(!productDetailsArr[product.id]) {
if (!promiseProductDetails) {
// $http returns a promise, which has a then function, which also returns a promise
promiseProductDetails = $http.get(product.id + '/productdetails.json').then(function(response) {
productDetailsArr[product.id] = response;
return productDetailsArr[product.id];
});
}
// Return the promise to the controller
return promiseProductDetails;
} else {
var deferred = $q.defer();
deferred.resolve(productDetailsArr[product.id]);
return deferred.promise;
}
Prevent AngularJS to return promise with cached data
Since $http returns a deferred object what you are doing here is actually overkill. When I changed your service to the following it seems to work fine.
Plunker
app.service('myService', function($http, $q) {
this.get = function(userId) {
console.log('Fetch data again using id ', userId);
var url = userId + '.json';
return $http.get(url, {timeout: 30000, cache: false});
};
});
Edit
To get your controller SecondCtrl
to update, the easiest thing to do, while keeping the structure of your code the same, is to broadcast the new data in an event defined in FirstCtrl
using $rootScope.$broadcast
and capture the broadcasted event in your other controller using $scope.$on
. I've updated the Plunker and now your data is in sync.
Modified loadUserFromMyService
function in FirstCtrl
:
$scope.loadUserFromMyService = function(userId) {
var promise = myService.get(userId);
promise.then(
function(data) {
console.log('UserData', data);
$scope.data = data;
$rootScope.$broadcast('newData', data);
},
function(reason) { console.log('Error: ' + reason); }
);
};
Added in SecondCtrl
:
$scope.$on('newData', function (evt, args) {
console.log('dataChanged', args);
$scope.data = args;
});
Build simple cache in Angularjs service for data provide from http request which return object reference
You can modify your functions as follows:
function cacheUsers(result) {
return (users) ? users : users = extract(result);
}
and
model.getUsers = function () {
return (users) ? $q.when(users) : $http.get(URLS.FETCH, {cache: true}).then(cacheUsers);
};
It provides additional cache check after fetch and enables built-in cache for the object.
I suggest you to read http://www.webdeveasy.com/angularjs-data-model/
AngularJS promise is caching
Well, you've only got a single var deferredLoad
per your whole application. As a promise does represent only one single asynchronous result, the deferred can also be resolved only once. You would need to create a new deferred for each request - although you shouldn't need to create a deferred at all, you can just use the promise that you already have.
If you don't want any caching, you should not have global deferredLoad
, isLoaded
and _deviceCollection
variables in your module. Just do
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
function getDevices(deviceIdsEndpoint) {
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
return RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
return _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
};
})
.value();
});
}
return {
destroyDeviceListing : function() {
// no caching - nothing there to be destroyed
},
getDeviceIdListing : function(deviceIdsEndpoint) {
return getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
return this.getDeviceIdListing(deviceIdsEndpoint)
.then(function(deviceIds) {
return _.chain(deviceIds)
.groupBy('deviceId')
.value();
});
}
};
}])
Now, to add caching you'd just create a global promise variable and store the promise there once the request is created:
var deviceCollectionPromise = null;
…
return {
destroyDeviceListing : function() {
// if nothing is cached:
if (!deviceCollectionPromise) return;
// the collection that is stored (or still fetched!)
deviceCollectionPromise.then(function(collection) {
// …is invalidated. Notice that mutating the result of a promise
// is a bad idea in general, but might be necessary here:
collection.deviceIds = undefined;
});
// empty the cache:
deviceCollectionPromise = null;
},
getDeviceIdListing : function(deviceIdsEndpoint) {
if (!deviceCollectionPromise)
deviceCollectionPromise = getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
return deviceCollectionPromise;
},
…
};
Using AngularJS $q promises in helper factory
I have something like this in my project:
app.factory("githubHelper", ["githubService", function(githubService) {
var promise = null;
function getInfo() {
if (!promise) {
promise = githubService.getAccount();
}
return promise;
}
return {
getInfo: getInfo
};
}]);
githubHelper.getInfo().then(function(data) {})
githubHelper.getInfo().then(function(data) {})
githubHelper.getInfo().then(function(data) {})
...
Related Topics
Nodejs Callbacks Simple Example
How to Change the Content of a <Textarea> with JavaScript
Difference Between "Change" and "Input" Event for an 'Input' Element
What Is Returned from a Constructor
How to Limit Google Autocomplete Results to City and Country Only
How to Transpose a JavaScript Object into a Key/Value Array
Remove All Event Listeners of Specific Type
JSON.Stringify() Array Bizarreness with Prototype.Js
How to Scroll to Specific Item Using Jquery
How to Find Indices of All Occurrences of One String in Another in JavaScript
Check If Event Exists on Element
Anonymous Class Instance - Is It a Bad Idea
How to Set Time Delay in JavaScript
Create Svg Tag with JavaScript
iPad Web App: Detect Virtual Keyboard Using JavaScript in Safari
How to Get Elements with Multiple Classes
How to Prevent Your JavaScript Code from Being Stolen, Copied, and Viewed