Event-Based Programming: What Async Has Over Sync

One of JavaScript's strengths is how it handles asynchronous (async for short) code. Rather than blocking the thread, async code gets pushed to an event queue that fires after all other code executes. It can, however, be difficult for beginners to follow async code. I'll help clear up any confusion you might have in this article.


Understanding Async Code

JavaScript's most basic async functions are setTimeout and setInterval. The setTimeout function executes a given function after a certain amount of time passes. It accepts a callback function as the first argument and a time (in milliseconds) as the second argument. Here's an example of its usage:

As expected, the console outputs "a", "b", and then 500 ms(ish) later, we see "c", "d", and "e". I use "ish" because setTimeout is actually unpredictable. In fact, even the HTML5 spec talks about this issue:

"This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected."

Interestingly, a timeout will not execute until all of the remaining code in a block has executed. So if a timeout is set, and then some long running function executes, the timeout will not even start until that long running function has finished. In actuality, async functions like setTimeout and setInterval are pushed onto an queue known as the Event Loop.

The Event Loop is a queue of callback functions. When an async function executes, the callback function is pushed into the queue. The JavaScript engine doesn't start processing the event loop until the code after an async function has executed. This means that JavaScript code is not multi-threaded even though it appears to be so. The event loop is a first-in-first-out (FIFO) queue, meaning that callbacks execute in the order they were added onto the queue. JavaScript was chosen for the language of node because of how easy it is to write this kind of code.


Ajax

Asynchronous JavaScript and XML (Ajax) forever changed the landscape of JavaScript. All of the sudden, a browser could update a web page without having to reload it. The code for implementing Ajax in different browsers can be long and tedious to write; however, thanks to jQuery (and other libraries), Ajax became an extremely easy and elegant solution to facilitate client-server communication.

Asynchronously retrieving data with jQuery's $.ajax is an easy cross-browser process, but it's not immediately evident as to what exactly happens behind the scenes. For example:

It's common, but incorrect, to assume the data is available immediately after calling $.ajax, but what actually happens is this:

The underlying XmlHttpRequest (XHR) object sends the request, and the callback function is set to handle the XHR's readystatechange event. Then the XHR's send method executes. As the XHR performs its work, an internal readystatechange event fires every time the readyState property changes, and it's only when the XHR finishes receiving a response from the remote host that the callback function executes.


Working With Async Code

Async programming lends itself to what's commonly referred to as "callback hell". Because virtually all async functions in JavaScript use callbacks, performing multiple sequential async functions result in many nested callbacks--resulting in hard to read code.

Many of the functions within node.js are async. So, code like the following is quite common.

It's also common to see client-side code, like the following:

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

The problem isn’t with the language itself; it’s with the way programmers use the language -- Async Javascript.

Named Functions

An easy solution that cleans nested callbacks is simply avoiding nesting more than two levels. Instead of passing anonymous functions to the callback arguments, pass a named function:

Additionally, the async.js library can help handle multiple Ajax requests/responses. For example:

This code executes the two asynchronous functions, and each function accepts a "done" callback that executes after the async function finishing running. When both "done" callbacks finish, the parallel function's callback executes and handles any errors or results from the two async functions.

Promises

From the CommonJS/A:

A promise represents the eventual value returned from the single completion of an operation.

There are many libraries that incorporate the promise pattern, and jQuery users already have a nice promise API available to them. jQuery introduced the Deferred object in version 1.5, and using the jQuery.Deferred constructor results in a function that returns a promise. A promise-returning function performs some sort of async operation and resolves the deferred upon completion.

This allows you to execute two asyncrhonous functions, wait for their results, and then execute another function with the results of the first two calls.

A promise represents the eventual value returned from the single completion of an operation.

In this code, the geocode method executes twice and returns a promise. The async functions then execute and call resolve in their callbacks. Then, once both have called resolve, the then executes, returning the results of the first two calls to geocode. The results are then passed to getRoute, which also returns a promise. Finally, when the promise from getRoute is resolved, the doSomethingCoolWithDirections callback executes.

Events

Events are another solution to communicate when async callbacks finish executing. An object can become an emitter and publish events that other objects can listen for. This type of eventing is called the observer pattern. The backbone.js library has this type of functionallity built in with Backbone.Events.

There are other mixin examples and libraries for emitting events, such as jQuery Event Emitter, EventEmitter, monologue.js, and node.js has a built-in EventEmitter module.

The Event Loop is a queue of callback functions.

A similar method of publishing messages uses the mediator pattern, used in the postal.js library. In the mediator pattern, a middleman for all objects listens to and publishes events. In this approach, one object does not have a direct reference to another, thereby de-coupling the objects from each other.

Never return a promise accross a public API. This couples the API consumers to using promises and makes refactoring difficult. However, a combination of promises for internal purposes and eventing for external APIs can lead to a nicely de-coupled and testable app.

In the previous example, the doSomethingCoolWithDirections callback function executes when the two previous geocode functions have completed. The doSomethingCoolWithDirections can then take the response it recieved from getRoute and publish the reponse as a message.

This allows other areas of the application to respond to the asynchronous callback without needing a direct reference to the request-making object. It's possible that multiple areas of a page need to update when directions have been retrieved. In a typical jQuery Ajax setup, the success callback would need to be adjusted when recieving a change of directions. This can become difficult to maintain, but by using messaging, updating multiple parts of the UI is much easier to work with.

Some other mediator pattern-based messaging libraries are amplify, PubSubJS, and radio.js.


Conclusion

JavaScript makes writing async code very easy. Using promises, eventing, or named functions eliminates the nasty "callback hell". For more information on async JavaScript, checkout Async JavaScript: Build More Responsive Apps with Less Code. Many of the examples from the post reside in a Github repository called NetTutsAsyncJS. Clone away!

Tags:

Comments

Related Articles