jQuery Tip #7 - Consolidating jQuery Ajax Calls

Interested in learning more about jQuery? Check out the jQuery Fundamentals course at Pluralsight.com.


Previous Tips:


As the use of Ajax continues to grow in popularity it's worth taking the time to think through how Ajax calls are structured and used in an application especially if re-use and maintenance are important. If you analyze a lot of the Ajax code out there you'll likely find that calls to the server are scattered throughout scripts and pages. For example, if an Ajax call is made to get a list of jobs, a call is made directly in the script that needs the data. If another page/script needs the same type of data another Ajax call is made. This results in code duplication and complicates maintenance since Ajax calls aren't consolidated.

While there's certainly nothing wrong with this approach (it gets the job done after all), you can get better re-use out of your Ajax calls by consolidating them into JavaScript objects. In this post I'll talk about how Ajax calls can be consolidated using JavaScript patterns and describe the benefits this approach provides.

The Case for Consolidation

JavaScript libraries such as jQuery make it so easy to make Ajax calls that we often don't think much about the code being written and where it's being used. For example, consider the following code that's used to retrieve customers from a WCF service and update a page with the retrieved data:

 

$('#GetCustomersButton').click(function () {
    $.getJSON('../CustomerService.svc/GetCustomers',
        function (data) {
            var cust = data.d[0];
            $('#ID').text(cust.ID);
            $('#FirstName').val(cust.FirstName);
            $('#LastName').val(cust.LastName);
        });
});

 

Although this code works fine, it can't be re-used across other pages that may also need customer data. If another page or script needs the same data then the getJSON() call will have to be re-written since the manner in which the returned data is processed will more than likely be different. In addition to this potential problem, the code is added into a script that may have completely different responsibilities that are unrelated to calling a service.

In the object-oriented world we strive to follow the single responsibility principle when designing classes to avoid duplication and provide better re-use. Following this same principle in JavaScript code makes a lot of sense and can lead to better re-use, simplified maintenance, and better overall organization of code in an application. By placing Ajax calls into a re-useable JavaScript object we can use them throughout an application more easily, minimize duplicate code, and provide a better maintenance story. Before jumping into how Ajax calls can be consolidated, let's review a key pattern called the Revealing Module Pattern that can be used to encapsulate JavaScript code.

 

Encapsulation with the Revealing Module Pattern

There are several different patterns that can be used to structure JavaScript code and provide a way for functions to be encapsulated inside of class-like structures (given that JavaScript doesn't officially support the concept of a class). One pattern that can be used is the Revealing Module Pattern. By using the pattern you can encapsulate variables and functions into an object, achieve re-use, simplify maintenance, and can also help avoid naming collisions. There are several other patterns such as the Prototype Pattern and Revealing Prototype Pattern that can be used (to name just two) but the Revealing Module Pattern provides a simple way to get started creating JavaScript objects that are similar in purpose to C# or Java classes.

The following code shows an example of some simple code that has no structure applied to it (I call it "Function Spaghetti Code"). With this approach variables and functions are scattered throughout a file with no rhyme or reason as to how they relate to each other. All of the variables and functions defined this way are automatically placed in the global scope which can lead to naming collisions.

 

var engine = 'V8';

function start() {
    alert('Car started ' + engine + ' engine');
}

function stop() {
    alert('Car stopped ' + engine + ' engine');
}

function turn() {
    alert('Car turned')
}

Listing 1. Function-based JavaScript Code with no encapsulation.

Listing 2 shows this same code refactored to follow the Revealing Module Pattern. In this simple example the code is encapsulated in an object named car. Following this pattern allows the code to be organized, variables and functions to be taken out of the global scope, and a re-useable object to be defined that can be used in one or more pages or applications.

 

var Car = function (engine) {
    var start = function () {
        alert('Car started ' + engine + ' engine');
    },
    stop = function () {
        alert('Car stopped ' + engine + ' engine');
    },
    turn = function () {
        alert('Car turned');
    };

    return {
        start: start,
        stop: stop,
        turn: turn
    };
}('V8');

Listing 2. Organize functions in an object to provide encapsulation, re-use, easier maintenance, and to help avoid naming collisions.

Looking through the code in Listing 2 you'll see that 3 functions are defined including start(), stop(), and turn(). All three are publicly exposed to consumers using the return object that's defined (an object literal). Any functions not listed in the return object are inaccessible to consumers making them similar to private members in object-oriented languages. Since the car object is self-invoked (note the parenthesis at the end of Listing 2) you can call it directly using code such as the following:

 

car.start();
car.stop();
car.turn();

 

If you want to create a new instance of car the code in Listing 3 can be used instead of the code shown in Listing 2. Notice that the Car object starts with an upper-case character so that the consumer knows to create a new instance of the object to use it. This is a common convention being used more and more among JavaScript developers.

 

var Car = function (engine) {
    var start = function () {
        alert('Car started ' + engine + ' engine');
    },
    stop = function () {
        alert('Car stopped ' + engine + ' engine');
    },
    turn = function () {
        alert('Car turned');
    };

    return {
        start: start,
        stop: stop,
        turn: turn
    };
};

Listing 3. Using the Revealing Module Pattern to create a Car object that can be created using the "new" keyword.


To use the Car object the following code can be written:

 

var car = new Car('V8');
car.start();
car.stop();
car.turn(); 

 

If you only need one object in memory as an application is running the code shown in Listing 2 works well. If you need multiple instances of an object the self-invoking parenthesis can be removed as shown in Listing 3.

Now that you've seen how the Revealing Module Pattern can be used to structure JavaScript code, let's see how it can encapsulate Ajax functions into an object.


Consolidating Ajax Calls

A sample application named Account at a Glance (download it here – an image from the application is shown below) built to demonstrate several HTML5, jQuery, and general JavaScript concepts relies on the Revealing Module Pattern to consolidate Ajax calls into a single object that can be re-used throughout the application. By following this pattern, an Ajax call used to retrieve market quotes can be defined in one place and then called from other scripts that may need the data. This approach provides several benefits including more modular, re-useable, and maintainable code. If something changes with an Ajax call you can go to one place to make the core modifications rather than searching through multiple scripts and pages to find where changes need to be made.

 

clip_image002


To access market quote data in the application the following call could be made as data is needed in a given script:

 

$.getJSON('/DataService/GetQuote', { symbol: sym }, function (data) {
    //process data here 
}); 


Although this code works, it's much better from a re-use and maintenance standpoint to consolidate calls into an object. Listing 4 shows an example of an Ajax-specific object named dataService that the Account at a Glance application uses to retrieve different types of JSON data from the server.

 

var dataService = new function () {
    var serviceBase = '/DataService/',
    getAccount = function (acctNumber, callback) {
        $.getJSON(serviceBase + 'GetAccount', { acctNumber: acctNumber },
          function (data) {
              callback(data);
          });
    },

    getMarketIndexes = function (callback) {
        $.getJSON(serviceBase + 'GetMarketIndexes', function (data) {
            callback(data);
        });
    },

    getQuote = function (sym, callback) {
        $.getJSON(serviceBase + 'GetQuote', { symbol: sym }, function (data) {
            callback(data);
        });
    },

    getTickerQuotes = function (callback) {
        $.getJSON(serviceBase + 'GetTickerQuotes', function (data) {
            callback(data);
        });
    };

    return {
        getAccount: getAccount,
        getMarketIndexes: getMarketIndexes,
        getQuote: getQuote,
        getTickerQuotes: getTickerQuotes
    };

} ();

Listing 4. Consolidating Ajax calls into a dataService object.

The dataService object follows the Revealing Module Pattern discussed earlier to encapsulate the various functions. A single instance is created initially at runtime (the dataService function is self-invoked as the script initially loads) that exposes four functions responsible for getting account, market, quote, and ticker data from the server.

Looking through each function in the dataService object you'll notice that they all accept a callback parameter. Because each function is re-useable, it won't know how to handle the data that's retrieved from a given service - processing of data is unique to the caller of the function. As a result, each function in dataService allows the caller to pass a callback function that is invoked once the data returns from the service to the client. An example of calling the dataService object's getAccount() function is shown next:

 

dataService.getAccount(acctNumber, renderAccountTiles);


When the data is returned the getAccount() function will invoke the renderAccountTiles callback function shown in Listing 5.

 

renderAccountTiles = function (data) {
    $('div.tile[id^="Account"]').each(function () {
        sceneStateManager.renderTile(data, $(this), 500);
    });
}

Listing 5. The renderAccountTiles() function handles data returned from the call to dataService.getAccount().

Note that a nested/inline callback function could be passed to getAccount() as well as shown next:

 

dataService.getAccount(acctNumber, function (data) {
    //Process data here
}); 

 

Anytime a script in the application needs to retrieve data from the server a call can be made to one of the dataService object's functions and required parameters can be passed along with the callback. This technique provides a flexible way for Ajax functionality to be consolidated into an object while remaining flexible as far as callbacks go.

This technique can be applied to other parts of an application as well to create additional objects that encapsulate related variables and functions. By focusing on objects rather than functions you can more efficiently organize code in an application and achieve many of the benefits mentioned earlier such as better re-use and simplified maintenance. Eliminating Function Spaghetti Code is definitely a good thing.

If you'd like additional details about structuring JavaScript code or building an end-to-end application check out my Structuring JavaScript Code or Building an ASP.NET MVC, EF Code First, HTML5, and jQuery courses from Pluralsight.


image

comments powered by Disqus

14 Comments

  • Nice work!
    I really like the structure in the data service. Like you said, its much better than littering you JavaScript with individual AJAX calls.

  • In the great manor of StackOverFlow comments....

    +1 for your Revealing Module Pattern description

  • Great posts!

    Thank you

  • Might be nice to return the deferred object from jQuery as the default pattern, to allow further success and error callbacks to be added later on.

    var dataService = new function () {
    var serviceBase = '/DataService/',

    getAccount = function (acctNumber, callback) {
    return $.getJSON(serviceBase + 'GetAccount', { acctNumber: acctNumber }).success(callback);
    };

    return {
    getAccount: getAccount
    };
    } ();

    var deferred = dataService.getAccount('1234', function(data) {
    alert(data.AccountName);
    });

    deferred.error(function() {
    alert('Error getting account');
    });

  • Your code listings are off - You've duplicated the "GetCustomersButton" code block, and if you remove the second one (which you have captioned as Listing 1), things should match up. Cheers!

  • Excellent technique! I was just moments away from adding yet another AJAX call to my project when I saw this, so I think I spend a bit of time and refactor everything into one place. Thanks.

  • Okay, so the Car object is not self-invoked because it is an "entity" so to speak, and the dataService function is self-invoked so that it can be used like a static class would be used? (as in, you don't have to new it up to use it)

    Are there any other criterion that might exist to decide whether to self-invoke an object when using the revealing module pattern?

  • Andy: I really like that idea. Thanks for the sample.

    Dan

  • EB: Thanks for commenting about the listings. Not sure I got them out of order but they're fixed now.

    Dan

  • Ryan: I generally take a simple approach with using "new" or self-invoked objects. If I only want one instance of an object in memory for a given app (such as the dataService object) then I'll go with the self-invoking option. If I know I'll need to create multiple instances of an object (such as multiple cars) then I'll leave off the final parenthesis. Not real sophisticated I realize, but it makes the choice pretty simple.

    Dan

  • Impressive. Excellent work!

  • Thanks Soren....glad you find it useful.

    Dan

  • nice blogs i like it

  • Your dataService could be DRY'ed up a bit more cos it has multiple $.getJSON calls. It'd also be a great way to illustrate how the revealing pattern can hide private methods.

Comments have been disabled for this content.