DEV Community

Pavel Polívka
Pavel Polívka

Posted on • Originally published at ppolivka.com

Reusing Promises in JavaScript

Lately, we started a project to improve the performance of our main app. We identified a few API calls we were calling a lot. Results of these calls can change but not very very often so it's not an issue to cache the result for a minute or so.

So I implemented a very easy cache that will reuse active promises and returns already resolved results for a minute after an initial resolution.

This article will go over the code in detail.
Let's start by simulating a parametrized API call.

function getData(key){
    return new Promise(function(resolve, reject) {
    console.log('starting get ' + key)

    setTimeout(() => {
        console.log('ending get ' + key)
        resolve(key);
    }, 1000);
  })
}
Enter fullscreen mode Exit fullscreen mode

Easy enough.

Now we need a few variables where we store our promises, results, and resolution times. We will also create a new function that we will be calling to get the cached results.

const _cacheValues = new Map();
const _cacheResolvedTime = new Map();
const _cachePromises = new Map();

const getDataCached = function (key) {
}
Enter fullscreen mode Exit fullscreen mode

The _cacheValues will hold already resolved values, _cachePromises will hold Promises in progress and _cacheResolvedTime will hold a time when the promise for the key was resolved last.

Now we will add a simple if statement that will be the basic stone of our cache.

if (_cacheValues.has(key)) {
  return Promise.resolve(_cacheValues.get(key));
} else if (_cachePromises.has(key)) {
        return _cachePromises.get(key);
} else {
  const promise = new Promise(function (resolve, reject) {
    return getData(key).then(data => {
      _cacheValues.set(key, data);
      _cachePromises.delete(key);
      const now = new Date().getTime();
      _cacheResolvedTime.set(key, now);
      resolve(data);
    });
  });
  _cachePromises.set(key, promise);
  return promise;
}
Enter fullscreen mode Exit fullscreen mode

If we already have a value for a key let's return that.
If we have a Promise in progress return that.
If we have no data for that key, we will trigger the original method. This trigger will be wrapping its promise so that we fill our cache on resolve.

Now we will add the time to the live feature. At the start of our new method, we will add.

const now = new Date().getTime();

if (_cacheResolvedTime.has(key)) {
  if ((now - _cacheResolvedTime.get(key)) > 60000) {
  _cacheResolvedTime.delete(param);
  _cacheValues.delete(key);
  _cachePromises.delete(key);
  }
}
Enter fullscreen mode Exit fullscreen mode

If we have it resolved and the resolution time is more than 60 seconds, we will remove it from our caches and continues to witch the rest of our logic.

Now we are done, we can test our code.

getDataCached('a').then(result => { console.log('first call outer: ' + result);
    getDataCached('a').then(result => { console.log('first call inner: ' + result); });
});

getDataCached('b').then(result => { console.log('first call outer: ' + result);
    getDataCached('b').then(result => { console.log('first call inner: ' + result); });
});

getDataCached('a').then(result => { console.log('second call outer: ' + result);
    getDataCached('a').then(result => { console.log('second call inner: ' + result); });
});

setTimeout(() => {
    getDataCached('a').then(result => { console.log('later call outer: ' + result);
    getDataCached('a').then(result => { console.log('later call inner: ' + result); });
});
}, 70000);
Enter fullscreen mode Exit fullscreen mode

You can see the console result and this whole code in this Fiddle.


If you like this article you can follow me on Twitter.

Top comments (2)

Collapse
 
dinniej profile image
Nguyen Ngoc Dat

The idea is great, but for caching, let the API do it for you. This won't work if the system you work with has to frequently update the data. With Redis and latest hardware we are having, it's just not necessary to having cache data on your client. I will still steal this tho.

Collapse
 
_genjudev profile image
Larson

I appreciate the reuse mindset. But you don’t want this. The API is always the truth. What you could do is having a “store” and update the store while deliver data from store. When accessing items you can call the API to update the items. So you have a fast response for your application and also you have nearly the latest version from your API.

When working with this concept you should have an state machine.

Of course it would be better if you just get events from your API and update the items when receiving auch event.

You don’t want to be 60 seconds late. Cache handling is done by your API.