Thoughts on Structuring an Ajax Callback

I've been doodling around the last few days, rewriting an auto-suggest jQuery plugin that we use at Ylgur for displaying instant search results to users as-they-type on many of our projects. It was time to build the thing properly and make it truly reusable.

Once I started to think about how I was going to structure my code and keep everything as modular as possible, I thought about ajax. I was saddened too think about those success and error methods I would eventually have to implement and about how their method-bodies would just keep on going endlessly. But then a blinding bulb of light illuminated the darkness and I thought about how great that unpronounceable $.Deferred object would be.

Why the $.Deferred object?

Anyone that has worked at all with $.ajax() has undoubtedly tried, at one point or the other, something like:

$.ajax({
    url: 'yourAwesomeUrl',
    success: function ( data ) {
        var name = data.name;
        doSomething( name );
    }
});

// Billion lines later
doSomethingElse( name );

Well… that just doesn't work. Inside of the success function, the name variable will of course return whatever the data.name is. However, outside of that function the name variable will be out of scope and simply return undefined.

And just for the sake of argument, let's say that the name variable was defined somewhere before and the doSomethingElse() function would execute correctly, then there is no guarantee in which order the two functions, doSomething() and doSomethingElse(), would run. This is after all an asynchronous function (hence the name Ajax — Asynchronous JavaScript and XML). It is highly probable that doSomethingElse() would be the first one to execute, even though it may be billions of lines later.

Enters $.Deferred.

On January 31st in 2011, jQuery 1.5 was released with an rewrite of the Ajax API and as its companion, the $.Deferred object hit the daylight. I'm not going go into the details about how the object works precisely but as an extract, I'll make it suffice to say that the $.ajax() method has the $.Deferred object built in.

With deferreds, multiple callbacks can be bound to a task’s outcome, and any of these callbacks can be bound even after the task is complete. The task in question may be asynchronous, but not necessarily.

This quote is from an article by Eric Hynds on Using Deferreds in jQuery 1.5 — it's still relevant even though we're now at jQuery 1.9.

The setup

Instead of having unorganized bunch of functions declared inside my plugin, I decided to namespace the functions inside an fn object.

var fn = {
    methodOne: function () {},
    methodTwo: function () {}
};

This makes it easy to simply call fn.methodTwo() whenever I feel like it.

For the ajax method itself I created an fetchData method which returns an Promise object. This makes it easy to attach callbacks to the methods of the ajax method.

// Fetch data from server
fetchData: function ( query ) {
    return $.ajax({
        url: config.ajax.url,
        type: config.ajax.type,
        data: { input: query }
    }).promise();
},

On each keyup event that the targeted jQuery object fires, I call fn.keyup. In that method I just do some basic house work to begin with, setting variables and such.

// On key up  
keyup: function ( e ) {  
   // Root element
   fn.root = this;

   // The query string
   fn.query = $(fn.root).val();

    // Shorthand variables
    var keyCode = e.keyCode,
        query = fn.query;

    // Perform actions only if the input isn't empty
    if (query.length) {
        fn.fetchData(query).then(fn.populate, fn.error);
    }
},

Nothing interesting really happens until on line 15. That's when I call fn.fetchData(), passing it the before defined query argument. Now the $.Deferred object finally shows it's awesomeness, the .then() method is actually a method of the $.Deferred object. It takes three arguments: doneFilter, failFilter and progressFilter. The two last ones are optional.

I created populate and error methods on the fn object that handle the callback of the success and error methods respectedly.

Conclusion

This setup makes for a much more readable code and in turn makes it more maintainable. The plugin is still in development but will hopefully become open source when the time is right.

If you have anything to add or just want to chat about nothing in particular, feel free to leave a comment, follow me on twitter or send me a line.