Tuesday, July 9, 2013

don't $.ajax everywhere

use an abstraction

jQuery has become the default way to not only deal with DOM but also with our ajax calls, and it's easy to see why. The issue though is that as we all welcome jQuery in to our codebase we aren't abstracting it, this is especially true with the ajax call.

From codebases I have found, it's pretty easy to chart a graph of code quality being inversely proportional to the amount of times $.ajax appears in the code. This is because $.ajax is an abstraction of a request, but is not an abstraction for making requests. The difference is subtle but very important.

I have never really used $.ajax myself but instead was lucky enough to learn abstracting out a manager for ajax requests when I was using the Closure Library. If I was asked about the most important things to know when constructing a large JavaScript application then ajax managment would be in my top 5 and I'd almost certainly point to goog.net.XhrManager as a place to start.

So why create an ajax manager? Well first off abstractions are a good thing because it gives us layers where we can inject specific code. Let's say you wanted to add some caching for requests, or log every request made from the system, how would you do that if you went directly in to $.ajax? You could either go in and replace every instance of $.ajax (time consuming) or override $.ajax (changing library code). If you have an xhr manager though you only need to change the code in one place - in fact you might even have made it so that manager could take in plugins so you could swap these things in and out with ease. Even better you could have different back ends for your manager which could allow you to pass in mock data or use local storage.

So the next project you start try using a separate class to manage your requests. It could be as simple as passing the query directly along to $.ajax:

var XHRManager = {
  send: function(options) {
    return $.ajax(options);
  }
}

but then you will control the request. As a final example I'll leave you with an example that will space requests with the same url at least a second apart:

define(['underscore','jquery'], function(_, $) {
 var cache = {};
 var ajax = $.ajax;
    return {
        send: function(options) {
            var url = options.url;
            if ((!options.type ||
                options.type.toUpperCase() == 'GET') &&
                !options.force) {
                if (!cache[url]) {
                    cache[url] = [];

     var runAjax = function() {
      if (!cache[url].length) {
       delete cache[url];
       return;
      }
      var opt = cache[url].shift();
      ajax(opt).success(function() {
       opt.defer.resolve.apply(opt.defer, [].slice.call(arguments, 0));
      }).fail(function() {
       opt.defer.reject.apply(opt.defer, [].slice.call(arguments, 0));
      });
      _.delay(runAjax, 1000);
     };

                } else {
                 options.defer = $.Deferred();
                    cache[url].push(options);
                    return options.defer;
                }
            }
            return ajax(options);
        },
        setup: _.bind($.ajaxSetup, $)
    };
});