Caching a Promise Object in Angularjs Service

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



Leave a reply



Submit