1

Quick Description of what my JS app should do...

I am using the JavaScript Promise library RSVP.js - https://github.com/tildeio/rsvp.js/ in my project.

Some things the code does...

  • Load 4-5 JOSN URLs using AJAX on 1st request and all followup requests will instead serve JSON from a cached variable.
  • JavaScript Promise() is used to load all JSON data before executing other code in the app.
  • Global variable window.jsonCache will hold the cached JSON and Promise() after they are loaded there first time with the AJAX request.
  • getJSON(jsonUrl, key) is a function that returns a Promise() and also makes an AJAX request or serves cached window.jsonCache[key-name-here] data if available.

The goal is to use JavaScript's Promise() and to also cache the JSON data returned from each AJAX request and serve the cached version on any repeat calls for the data instead of making duplicate AJAX requests each time a script on the page calls for the data.

I have started work on a demo using the best of my JavaScript knowledge which I am still learning new JS daily. I am much stronger in PHP but progressing in JS.


The Problem where I need assistance/help

I believe I have it mostly working and almost to my needs. My problem is on my JSFiddle demo http://jsfiddle.net/jasondavis/nzzh1hee/5/ which runs all the code explained below. The demo loads my JSON data with an AJAX request for all 4 of my JSON URLs. It should then cache the result for future calls to this data to be served from. It is instead making repeat AJAX calls the next time I call/run the code.

So somewhere it seems my caching mechanism is not working correctly or possibbly some other issue even.

The final result should run my Promise() code regardless if the JSON data is served from an AJAX request or from a cached variable.

I need help to get this part of the flow working please. The JSFiddle above has a working demo of this code which you can view the Dev tools console to see my debug data.


UPDATE

SO User Kevin B identified my problem source being that my 2nd call to the data is called before my 1st call has time to cache the values which is why repeat AJAX calls are being made!

I could use some help in modifying this existing code to get around this issue and as mentioned by user Jared Smith if I could Memoize the AJAX function getJSON(jsonUrl, key) would be a good method but I basically though that is what I had done so I could use some help here please!


Side note...

The JSFiddle demo uses /echo/json/ as the endpoints for all the AJAX request which return no data in the demo. So I have also tested locally loading real JSON data from my AJAX request just to make sure that was not the source of my issue and it did not make a difference.


All code and text below this line is just a breakdown of my code explained better. The text above is enough to understand the problem and goal and the JSFiddle demo runs the code below


So I will briefly try to break down my demo code below to explain some key parts of it...

jsonCache will hold the cached JSON data after a successful AJAX load of it for future calls to the JSON data to be served from this cache instead of making a new AJAX request.

// Global variable to hold cached JSON data retrieved from AJAX requests
window.jsonCache = {
  users: '',
  milestones: '',
  tags: '',
  task: ''
};

getJSON(jsonUrl, key) is a function that returns a Promise() and also make an AJAX request or serves cached data if available. The jsonUrl is the URL to the JSON data on server and the key is a name assigned to access the cached data later on.


AJAX request to get JSON data and populate cache variables

I then use if(isEmpty(window.jsonCache[key])) { } to make the AJAX request if the data is not previously cached.

The AJAX request will call a handler() callback function on success which in turn also calls the Promise() resolve(this.response) function passing the JSON data into the resolve function. On AJAX failure it will call the Promise() Reject() function.

In the }else{ statement it will simply return the cached version of the data by accessing it with its key value and will not make a duplicate AJAX request for this particular key of data/url.


JSFiddle Demo using the code from below http://jsfiddle.net/jasondavis/nzzh1hee/5/

Utility Functions Used in code below

// RSVP.js Promise Library used from https://github.com/tildeio/rsvp.js/

// utility function to check for empty variables
function isEmpty(str) {
  return (!str || 0 === str.length  ||  str === '');
}

// Global variable to hold cached JSON data retrieved from AJAX requests
window.jsonCache = {
  users: '',
  milestones: '',
  tags: '',
  task: ''
};

PROMISE() FUNCTION TO MAKE AJAX REQUEST FOR JSON DATA

// AJAX function to load JSON data using Promise()
var getJSON = function(url, key) {
  var promise = new RSVP.Promise(function(resolve, reject){

      // If cached data is not set then make AJAX requiest to get it
      if(isEmpty(window.jsonCache[key])) {

        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

        console.log('---- "client" XMLHttpRequest/AJAX  variable ======= ',client);     

        ////////////// temp test
        //      jsonCache[key] = ' AJAX Response';   
        ////////////// end temp test    

        function handler() {
          if (this.readyState === this.DONE) {
            // On AJAX success, resolve() our Promise() and set result to cached variable 
            // to avoid duplicate AJAX requests for this jsonCache[key] Data where "key"
            // is used to assign to each AJAX endpoint URL/request of JSON data... 
            // (milestones, tasks, users, etc...)
            if (this.status === 200) {
                window.jsonCache[key] = this.response;
                window.jsonCache[key] = key+' AJAX Response';

                console.log('---- window.jsonCache['+key+'] ====== ',window.jsonCache[key]);

                // Resolve() the Promise() on AJAX success
                resolve(this.response);

            // On AJAX failure, reject() our Promise()
            }else{
                reject(this);
            }
          }
        };

      // If JSON data for this key is already cached, then return the cached version 
      // instead of making a new AJAX request!
      }else{
        console.log('---- window.jsonCache['+key+'] ====== ',window.jsonCache[key]);

        // Return cached version of JSON data as a Promise() and pass into the 
        // Resolve() function
        window.jsonCache[key] = key+' CACHED Response';
        resolve(window.jsonCache[key]);
      }

  });

  return promise;
};

EXAMPLE USAGE DEMO USING THE CODE FROM ABOVE

First call/load of JSON data which loads through AJAX request and caches results to variables.

// EXAMPLE USAGE DEMO
// usage loading multiple AJAX calls using Promises
// Each of these 4 JSON URLs below will be loaded using Promise() and AJAX requests on 
// first call to them.  2nd call to them should instead serve a cached version stored in 
// a global variable window.jsonCache.KEYNAME-HERE
var promises = {
    users: getJSON('/echo/json/', 'users'),
    milestones: getJSON('/echo/json/', 'milestones'),
    tags: getJSON('/echo/json/', 'tags'),
    task: getJSON('/echo/json/', 'task')
};     

// Load the AJAX JSON function above for each Promise()
// Handles success, finally() for every load, and error for when error occurs
RSVP.hash(promises).then(function(results) {
    console.log(results);
    console.log(results.users); // print the users JSON results
}).finally(function(){
    console.log('finally() function ran on success and failure.... It is always ran!');
}).catch(function(reason){
    console.log('[ERROR] REASON:',reason.statusText); //if any of the promises fails.
});

2nd call to load JSON data which should load from cached variable but is still loading from a new AJAX requests

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//
//   Below is another call to load the same 4 JSON data to test and see if it is 
//   served from a cached variable instead of making a duplicate 2nd AJAX request for each item.
//
//
////////////////////////////////////////////////////////////////////////////////////////////////


// EXAMPLE USAGE DEMO
// usage loading multiple AJAX calls using Promises
// Each of these 4 JSON URLs below will be loaded using Promise() and AJAX requests on 
// first call to them.  2nd call to them should instead serve a cached version stored in 
// a global variable window.jsonCache.KEYNAME-HERE
var promises = {
    users: getJSON('/echo/json/', 'users'),
    milestones: getJSON('/echo/json/', 'milestones'),
    tags: getJSON('/echo/json/', 'tags'),
    task: getJSON('/echo/json/', 'task')
};     

// Load the AJAX JSON function above for each Promise()
// Handles success, finally() for every load, and error for when error occurs
RSVP.hash(promises).then(function(results) {
    console.log(results);
    console.log(results.users); // print the users JSON results
}).finally(function(){
    console.log('finally() function ran on success and failure.... It is always ran!');
}).catch(function(reason){
    console.log('[ERROR] REASON:',reason.statusText); //if any of the promises fails.
});
JasonDavis
  • 48,204
  • 100
  • 318
  • 537
  • 1
    Nice example I have wanted to do something really similar in my own project and your code is a big help in me understanding how I can approach this, thanks! Sorry I can't be of help – Dave Riley Oct 22 '15 at 18:38
  • Your second set of requests are sent before the first set are done, thus before the cache has a value so they also send requests. You should be storing promises in the cache and always returning promises. – Kevin B Oct 22 '15 at 18:54
  • @KevinB makes perfect sense. The problem is I thought I was returning a promise in the cached var! Do you mind showing a quick snippet of where I went wrong maybe if you have time? IN either case thanks for pointing out the source of problem – JasonDavis Oct 22 '15 at 18:56
  • memoize your ajax function – Jared Smith Oct 22 '15 at 18:56
  • @JaredSmith That was my goal and I thought I had accomplished that but apparently not! Im a bit stuck now – JasonDavis Oct 22 '15 at 18:58

1 Answers1

2

Your second set of requests are sent before the first set are done, thus before the cache has a value so they also send requests. You should be storing promises in the cache and always returning promises.

Here's a simplified example using a made up sendRequest method that returns a promise.

var cache = {};

function getData (url, key) {
  if (!cache[key]) { 
    cache[key] = sendRequest(url);
  }
  return cache[key];
}

var promisesOne = {
    users: getData('/echo/json/', 'users'),
    milestones: getData('/echo/json/', 'milestones'),
    tags: getData('/echo/json/', 'tags'),
    task: getData('/echo/json/', 'task')
};

var promisesTwo = {
    users: getData('/echo/json/', 'users'),
    milestones: getData('/echo/json/', 'milestones'),
    tags: getData('/echo/json/', 'tags'),
    task: getData('/echo/json/', 'task')
};

http://jsfiddle.net/jv8kzdy9/

The key difference between my example and your code is that mine always returns the same promise for any one key, meaning both promiseOne and promiseTwo above have the same set of promises. Yours instead returns a new promise each time getJSON is called. This difference is important because without it, the second call has no way of knowing when the first is complete, it can only deal with what is currently in the cache object.

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • Thank you very much! A question. Your `sendRequest()` compared to my current `getJSON(url, key)` which has this on the first line... `var promise = new RSVP.Promise(function(resolve, reject)` You mentioned mine creates new Promise on each call which I think you are referring to this line? If so, DO you know where or when I would call for a new promise? – JasonDavis Oct 22 '15 at 19:07
  • Basically, your `if cache is empty` conditional would need to happen before creating the promise, because you only want to create a promise if the cache is empty. Otherwise, you skip that step and just return the promise. – Kevin B Oct 22 '15 at 19:08
  • my `getData` is equivalent to your `getJSON` method, and my `sendRequest` is equivalent to what you are doing with `RSVP` – Kevin B Oct 22 '15 at 19:09
  • I see now in regards to last comment I just now got to see your JSFiddle so I see what you mean. I was actually previously using the jQuery style `return $.ajax(url);` in a return function like your demo but after so much reading about how jQuery does Promises wrong and that in v3 of jQuery they plan to re-build the Promise system to be compatible with the other libraries and standard made me think that now might be the good time to switch it out with a lib like RSVP.js – JasonDavis Oct 22 '15 at 19:13
  • I just used it because it was convenient. :) I don't use jQuery in my own code very much anymore. – Kevin B Oct 22 '15 at 19:14
  • Here's your code updated, all i did was remove the else, and move the if conditional outside of the promise. http://jsfiddle.net/nzzh1hee/6/ – Kevin B Oct 22 '15 at 19:16
  • Thanks you cleared up some things I wasnt 100% about in your answer and comments. My understanding is that I could basically take your demo and simply drop my `RSVP promise creation code right into your `sendRequest()` and that would take care of everything if I understand correctly. Ill quite being lazy and just test it out! thanks again – JasonDavis Oct 22 '15 at 19:17
  • haha. i already did that step for you. :) continue being lazy! – Kevin B Oct 22 '15 at 19:17
  • I see, you're good! It took me a day basically just to get my head around the demo I made here and get to this point! Im finally getting it though I think. See you on the next one! – JasonDavis Oct 22 '15 at 19:19