Friday, December 5, 2014

How Asynchronous JavaScript Promises Work

The Promise object in the proposed ECMAScript 6 specification makes performing asynchronous operations easier. Not necessarily Ajax requests, but local function calls. These functions themselves may or may not depend on an external resource, but the key to promises is that the API looks and behaves the same. Promises, out of the box, are asynchronous. Let's illustrate the idea with some code:

new Promise(function(resolve, reject) {
    console.log('native', 'working');
}).then(function() {
    console.log('native', 'done');

(function() {
    console.log('jquery', 'working');
    return $.Deferred().resolve().promise();
})().then(function() {
    console.log('jquery', 'done');

The first promise is a native Promise — not all browsers support this yet. The second promise uses the jQuery implementation. Both experiments log the same thing, but the order of the output is what's interesting:

native working
jquery working
jquery done
native done

You can clearly see that while the jQuery approach is synchronous, the native approach isn't — it's resolve callback is called after all the jQuery code executes. What's going on here? JavaScript code executes synchronously through what's called a job queue. The JavaScript engine puts pieces of JavaScript code in this queue, and executes them in FIFO order.

However, resolution callbacks have their own promise queue. And it's up to the engine's implementation to decide which queue the next job comes from. As you can see from the output above, the job queue executes:
  • console.log('native', 'working')
  • console.log('jquery', 'working')
  • console.log('jquery', 'done')
There's obviously more code executed here, but these are the relevant parts concerning the output. The promise queue executes:
  • console.log('native', 'done')
You can see that the engine interweaves between the two queues as it picks up new jobs to run. What's interesting about this is that it negates the need to hack the JavaSctipt execution context using setTimeout(). You'd normally do this when you need to divide things up, long-running code that freezes the UI.