Why Asynchronous JavaScript Matters

Building smooth, responsive HTML5 applications depends on synchronizing data fetching, processing, animations, and UI updates. Unlike native desktop environments, browsers give applications a single thread for all DOM access and user interface logic. Every UI-touching work unit must therefore stay small and efficient, and developers must lean on any asynchronous capabilities the browser provides.

The Browser's Asynchronous Toolbox

Browsers expose several asynchronous APIs, including the familiar XHR (AJAX), IndexedDB, SQLite, HTML5 Web Workers, and GeoLocation. Even some DOM actions, like CSS3 animations, trigger asynchronous events via transitionEnd.

These APIs fall into two patterns. Event-based APIs have developers register a handler on an object and then trigger the action the browser executes, firing the event on the main thread when done. An XHR request looks like this:

// Create the XHR object to do GET to /data resource  
var xhr = new XMLHttpRequest();
xhr.open("GET","data",true);

// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
alert("We got data: " + xhr.response);
}
},false)

// perform the work
xhr.send();

CSS3 animations follow the same pattern:

// get the html element with id 'flyingCar'  
var flyingCarElem = document.getElementById("flyingCar");

// register an event handler 
// ('transitionEnd' for FireFox, 'webkitTransitionEnd' for webkit) 
flyingCarElem.addEventListener("transitionEnd",function(){
// will be called when the transition has finished.
alert("The car arrived");
});

// add the CSS3 class that will trigger the animation
// Note: some browers delegate some transitions to the GPU , but 
//       developer does not and should not have to care about it.
flyingCarElemen.classList.add('makeItFly') 

Callback-based APIs, such as SQLite and Geolocation, take a function argument that the implementation calls back with the result:

// call and pass the function to callback when done.
navigator.geolocation.getCurrentPosition(function(position){  
        alert('Lat: ' + position.coords.latitude + ' ' +  
                'Lon: ' + position.coords.longitude);  
});  

This design lets the browser decide whether to implement the operation synchronously or asynchronously behind a single consistent API for developers.

Designing Application APIs for Asynchrony

Well-architected applications should expose their low-level APIs asynchronously too, especially for I/O or heavy computation. A data access API that blocks the calling thread freezes the UI while fetching network, SQLite, or IndexedDB data, which can cripple the user experience.

// WRONG: this will make the UI freeze when getting the data  
var data = getData();
alert("We got data: " + data);

The correct pattern is to make any API that could take time asynchronous from the start, as retrofitting synchronous code is difficult:

getData(function(data){
alert("We got data: " + data);
});

This keeps UI code asynchronous-centric from the start and leaves room for the underlying implementation to remain synchronous if that's sufficient. Not every API needs this treatment — only those doing I/O or tasks taking longer than about 15ms.

Handling Failures Without try/catch

Traditional try/catch breaks with asynchronous code because errors occur on other threads. Callback-based APIs need a second function argument to notify the caller when processing fails:

// getData(successFunc,failFunc);  
getData(function(data){
alert("We got data: " + data);
}, function(ex){
alert("oops, some problem occured: " + ex);
});

Event-based APIs typically handle this by an event handler querying the source event or object. Chaining multiple asynchronous calls this way quickly gets unwieldy. Waiting for two APIs to complete before a third produces rapidly escalating complexity:

// first do the get data.   
getData(function(data){
// then get the location
getLocation(function(location){
alert("we got data: " + data + " and location: " + location);
},function(ex){
alert("getLocation failed: "  + ex);
});
},function(ex){
alert("getData failed: " + ex);
});

Reusing these multi-step calls across a large application gets harder still, pushing developers toward building their own caching and coordination infrastructure.

The Promise Pattern and jQuery.Deferred

The Promise pattern offers a clean solution to these coordination pains. Similar to Java's Future, it defines that an asynchronous API returns a Promise object — a placeholder for a result that will be resolved with data later. The caller registers a done(successFunc(data)) callback on the Promise to receive that data when it becomes ready.

The getData example becomes:

// get the promise object for this API  
var dataPromise = getData();

// register a function to get called when the data is resolved
dataPromise.done(function(data){
alert("We got data: " + data);
});

// register the failure function
dataPromise.fail(function(ex){
alert("oops, some problem occured: " + ex);
});

// Note: we can have as many dataPromise.done(...) as we want. 
dataPromise.done(function(data){
alert("We asked it twice, we get it twice: " + data);
});

The caller gets a dataPromise and calls .done to register a callback, and can register as many .done or .fail handlers as needed. The .fail method handles failures. Both succeed and failure registrations can occur any number of times because the jQuery implementation manages registration and invocation.

jQuery makes composing more advanced synchronization easy with helpers like $.when. The nested getData/getLocation callbacks compress to:

// assuming both getData and getLocation return their respective Promise
var combinedPromise = $.when(getData(), getLocation())

// function will be called when both getData and getLocation resolve
combinePromise.done(function(data,location){
alert("We got data: " + dataResult + " and location: " + location);
});  

Implementing an asynchronous function with jQuery.Deferred is straightforward:

function getData(){
// 1) create the jQuery Deferred object that will be used
var deferred = $.Deferred();

// ---- AJAX Call ---- //
XMLHttpRequest xhr = new XMLHttpRequest();
xhr.open("GET","data",true);

// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
    // 3.1) RESOLVE the DEFERRED (this will trigger all the done()...)
    deferred.resolve(xhr.response);
}else{
    // 3.2) REJECT the DEFERRED (this will trigger all the fail()...)
    deferred.reject("HTTP error: " + xhr.status);
}
},false) 

// perform the work
xhr.send();
// Note: could and should have used jQuery.ajax. 
// Note: jQuery.ajax return Promise, but it is always a good idea to wrap it
//       with application semantic in another Deferred/Promise  
// ---- /AJAX Call ---- //

// 2) return the promise of this deferred
return deferred.promise();
}

When getData() is called, it creates a deferred object and immediately returns its Promise so the caller can register handlers. When the XHR completes, the deferred either resolves, triggering all done functions along with other Promise callbacks like then and pipe, or it rejects, invoking all fail handlers.

Practical Uses for Deferred

Deferred objects prove valuable in several application areas:

  • Data access: Exposing data APIs as Deferred is usually a solid design choice, obvious for remote calls but also useful for local data backed by asynchronous SQLite or IndexedDB. $.when and .pipe make it easier to coordinate and chain complex sub-queries.
  • UI animations: Coordinating transitionEnd events across mixed CSS3 and JavaScript animation gets tedious quickly. Wrapping animation functions as Deferred simplifies orchestration and adds flexibility — even a simple wrapper like cssAnimation(className) that returns a Promise resolved on transitionEnd can help.
  • UI component display: When a user interface shows different parts progressively, encapsulating component lifecycle in Deferred objects gives developers more control over when parts of the UI appear.
  • Normalizing browser APIs: Wrapping browser asynchronous API calls as Deferred takes only a few lines each but unifies the application's asynchronous model across browser, application-level, and compound calls.
  • Caching handles: Because Promise callbacks can register before or after an asynchronous call completes, a Deferred object can serve as a caching handle — a manager keeps Deferreds for requests and hands out Promises for matching ones. Callers never need to check whether a call is resolved or in progress; their callback behaves identically.

The Deferred concept is straightforward, but mastery takes practice. In a browser environment that restricts threading, asynchronous JavaScript is essential to serious HTML5 application development. The Promise pattern is a real step forward as a robust solution to reliable, flexible asynchronous application logic.