Caching a promise object in AngularJS service
Asked Answered
E

3

35

I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.

I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.

var data = null;
var deferredLoadData = null;

function loadDataPromise() {
  if (deferredLoadData !== null)
    return deferredLoadData.promise;

  deferredLoadData = $q.defer();

  $http.get("data.json").then(function (res) {
    data = res.data;
    return deferredLoadData.resolve();
  }, function (res) {
    return deferredLoadData.reject();
  });

  return deferredLoadData.promise;
}

So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.

But is it a good solution to cache Promises?

Endothelium answered 11/9, 2013 at 15:5 Comment(2)
this line is incorrect: return deferredLoadData.resolve();. You should simply deferredLoadData.resolve();, and deferredLoadData.reject();. The return statement makes the whole promise return undefined.Northamptonshire
@Northamptonshire - you're right. It doesn't make sense. In fact, it doesn't do anything. (but it also doesn't break anything, since no chain is used)Endothelium
C
48

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).

Campman answered 11/9, 2013 at 15:35 Comment(4)
why not use the $cacheFactory? Doesn't that give more flexibility to access the cache from throughout the module?Ranch
@Darryl: Sorry, I don't know that much about Angular, my answer is based on promises only.Campman
@Darryl: I guess the main difference is that by using $cacheFactory you cache the result of a service call, and by "dataPromise" you cache the promise itself. In the second way, if you call the service twice, and the first call did not return yet, the second call will not be triggered, and when the first call returns both promises are resolved. This is a big advantage in some casesPaez
you saved me a lot of redundant and WRONG code in the endMoynihan
A
3

For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.

Example:

function loadCached() {
   return deferCacheService.getDeferred('cacke.key1', function () {
      return $http.get("data.json");
   }); 
} 

and consume

loadCached().then(function(data) {
//...
});

One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check

if (defer && defer.promise.$$state.status === 0) {
   return defer.promise;
}

otherwise you will be doing duplicate calls to backend.

Arundell answered 30/11, 2015 at 14:36 Comment(0)
R
0

This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.

const asyncTask = (cache => {
  return function(){
    // when called first time, put the promise in the "cache" variable
    if( !cache ){
        cache = new Promise(function(resolve, reject){
            setTimeout(() => {
                resolve('foo');
            }, 2000);
        });
    }
    return cache;
  }
})();

asyncTask().then(console.log);
asyncTask().then(console.log);

Explanation:

Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)

Racehorse answered 4/10, 2018 at 7:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.