EvilDr
EvilDr

Reputation: 9610

Prevent duplicate $.ajax calls using result cache

A similar question has been asked before, but I don't believe it overcomes the challenges in this case because my function calls are all together, so please bear with me (I'll delete the question if appropriate).

I have a number of dashboard widgets that each make an $.ajax call, receive a JSON result and then process that to render a Google chart. The widgets can be used multiple times, so there are some duplicated AJAX calls occurring, e.g.

RenderChart('/api/LoginCount?DaysPrevious=7', 'ColumnChart'); // some parameters removed, for brevity
RenderChart('/api/LoginCount?DaysPrevious=7', 'AreaChart');
RenderChart('/api/LoginCount?DaysPrevious=7', 'Table');

The problem is that this generates multiple calls to the same URL, which is extremely wasteful. I saw in the linked question that an object can be used to cache the results, but when I applied this, it didn't seem to work because the second call to RenderChart (immediately after the first) saw there was no data (yet) in the cache, and called the URL again.

My code is:

function LoadDataFromApi(apiUrl) {
    return $.ajax({
        type: 'GET',
        url: apiUrl,
        dataType: "json",
        success: function (data) { }
    });
}

function RenderChart(apiUrl, chartElementId, chartType, chartOptions) {
    $.when(LoadDataFromApi(apiUrl)).done(function (data) {
        var el = $('#' + chartElementId);
        try {
            var arrayOfArrays = BuildGoogleArrayFromData(data); // Transform JSON into array of arrays (required by Google Visualization)
            $(el).empty();
            if (arrayOfArrays.length == 0) { // Data found?
            $(el).append('<p class="noData">No data was found.</p>');
        } else {
            var wrapper = new google.visualization.ChartWrapper({ // alert(wrapper.getChartType()); // wrapper is the only way to get chart type
                chartType: chartType,
                dataTable: google.visualization.arrayToDataTable(arrayOfArrays, false),
                options: chartOptions,
                containerId: chartElementId
            });
        wrapper.draw();
        }
    }
    catch (ex) {
        $(el).append('<p class="error">An error occurred: ' + ex.message + '</p>');
    }
    });
}

Ideally it would be good to cache the arrayOfArrays value, as at this point all additional processing is also complete. However, getting JavaScript to see what other API calls are in progress, and wait for them is where I'm struggling. Is this possible to achieve?

If anyone can handhold me into achieving both I'll put a bonus on the question. I read about promises, but I need to support IE9+.

Upvotes: 1

Views: 1974

Answers (1)

31piy
31piy

Reputation: 23869

I can think of making a cache map with URL as its key, and the AJAX request as its value. We can change your LoadDataFromApi function to leverage this cache, and return appropriate AJAX request, if exists, else make a new request.

Following is a snippet of how it can be done.

var requestCache = {};

function LoadDataFromApi(apiUrl) {
  if (!requestCache[apiUrl]) {
    requestCache[apiUrl] = $.ajax({
        type: 'GET',
        url: apiUrl,
        dataType: "json"
    });
  }

  return requestCache[apiUrl];
}

This way, you can call LoadDataFromApi without any limit, and chain your promise handlers like this:

LoadDataFromApi('http://fake.url')
  .then(function(data) {
    // use the data in one widget
  })

LoadDataFromApi('http://fake.url')
  .then(function(data) {
    // use this data in another widget
  })

// ... and so on

This way the AJAX call for a particular URL will be made only once, and the result will be shared among the promise handlers.

Upvotes: 1

Related Questions