Asynchronous programming in JavaScript using promises

Expand
10 out of 11 rated this helpful Rate this topic

Asynchronous programming in JavaScript using promises

[This documentation is preliminary and is subject to change.]

Avoiding synchronous execution in single-threaded languages like JavaScript is necessary in order to create apps that are responsive and high performing. Windows Library for JavaScript provides a consistent and predictable mechanism called a promise that simplifies asynchronous programming.

The necessity of asynchronous programming in JavaScript

JavaScript is a single-threaded language. This means that invoking a long-running process will block all execution until that process completes. For example, let's say we have an imaginary library that allows querying a web service. Perhaps we invoke it like this:



var result = myWebService.get("http://www.contoso.com");


While we are waiting for get to return, all other execution in our environment has halted. This means that UI elements will be unresponsive, animations will pause, and no other code in our application is executing.

The solution to this problem is to avoid synchronous execution as much as possible. In general, you will encounter a few basic patterns. These patterns can all be described as a way of designating a function to be executed at some later point in time. Which pattern you encounter will depend on the API you are using. For instance, when working with the HTML DOM the various elements expose a number of events. Subscribing to these events allows a developer to execute code in an asynchronous fashion. If our imaginary web service library followed this pattern, it might look like this:



myWebService.addEventListener('completed', function(result) { /* do something */});
myWebService.get("http://www.contoso.com");


Now the call to get does not block execution in our JavaScript (it is presumed that the actual work of getting the results from the web service occurs outside of the JavaScript runtime). Instead, the work is merely initiated and once it is finished the "completed" event is raised and our handler function is invoked. This pattern is useful when you have more than one function that needs to be executed when the long running process completes.

In the case of the second pattern, the provided function is actually passed as an argument to the initiating function. If our imaginary web service library followed this pattern, it might look like this:



myWebService.get("http://www.contoso.com", function(result) { /* do something */});


The function that is passed as an argument is referred to as the callback function, because it is used to call back into the code that initiated the long running process.

Another example of the callback pattern would be using JavaScriptsetTimeout function to animate a DOM element. setInterval takes two parameters: a callback function and a frequency in milliseconds. It invokes the callback function at a regular interval determined by the frequency. This example moves an element across the screen at a rate of 4px per second.



var start = 0;
var interval = setInterval(function() {
   start = start + 1;
    element.style.left = start + 'px';
}, 250);


Challenges with basic asynchronous patterns

One of the challenges with asynchronous programming is that it can become complicated very quickly. Many of the standard JavaScript APIs rely heavily on callbacks, and it is common to find that logic that would be linear in other languages becomes a complex set of nested callbacks in JavaScript. For example, you might invoke an asynchronous operation whose callbacks will in turn invoke another asynchronous operation and so. The logic is complicated further when you consider other issues such as error handling.

Debugging asynchronous code can also be challenging. For example, the use of anonymous inline functions can make reading the call stack problematic. Exceptions that are thrown deep in a set of callbacks might not be propagated up to a function that initiated the chain, making it difficult to determine exactly where a bug is hiding.

Asynchronous patterns in Metro style apps with JavaScript

The Windows Library for JavaScript library mitigates these problems by implementing the proposed standard Common JS Promises/A. In Windows Library for JavaScript , a promise object "promises" to return a value at some point in the future. It allows you to construct chains of asynchronous code that are easier to read. In addition, all of the Windows Runtime APIs that are exposed to Metro style app using JavaScript are wrapped in promise objects. This allows JavaScript developers to use native Windows APIs while leveraging existing standards that are emerging in the broader JavaScript community. Having the promise abstraction in Windows Library for JavaScript is very important, since many interactions with Windows Runtime are asynchronous.

Promises in Windows Library for JavaScript however are independent of Windows Runtime and developers can readily use them when constructing their own pure JavaScript libraries.

Basic use of a promise

A promise in Windows Library for JavaScript is an object. The most frequently used method on a promise object is then. The first parameter of then is a function to invoke when the promise completes successfully. If we modify our imaginary web service library to uses promises, it would look like this:



myWebService.get("http://www.contoso.com").then(function(result) { /* do something */});


At first glance, this looks very similar to passing a callback. However, promises provide you with a standard and predictable means of working with asynchronous code. This becomes more apparent as we explore the additional features that promises offer.

In addition to a completion callback, then can also accept a function for handling errors and a function for reporting progress (it's not guaranteed that all promises will implement reporting progress, this depends on the nature of the library). If we used these with our web service library it would look like this:



myWebService.get("http://www.contoso.com")
    .then(
       function(result) { /* do something */},
       function(error) { /* handle error */},
       function(progress) { /* report progress */}
     );


The composability of promises

A key feature of promises is their composability. Let's say that we want to store the results of our web service call into some sort of database and then report the status to the console. We'll also pretend that we have an API for accessing this database that uses promises. Our code would look like this:



myWebService.get("http://www.contoso.com")
    .then(function(result) { 
        return myDb.add(result); // this function returns a promise too
    })
    .then(function() {
        console.log('data successfully saved');
    }, function(error) {
        console.log('an error occurred while saving:');
        console.dir(error);
    });


Since myDb.add returns a promise, and we return that promise inside the completed function for myWebService.get, we can chain another call to then which we use to report the status of the operation.

Making your own promises

You can easily create your own promise-based API using Windows Library for JavaScript. Let's say that we have a need to display a confirmation dialog that asks a user to choose between okay and cancel. We'd like to make this a reusable module based on promises. We'd like the interface for our confirmation module to look like this:



confirm.show('Are you sure?' })
        .then(function (ok) {
            return ok
                ? doSomething()
                : dontDoSomething();  
        });


Both doSomething and dontDoSomething could return a promise as well to continue the chain.

Let's consider how we would implement show:



function show(caption) {
    var ok = WinJS.Utilities.query('button#ok', element)[0],
        cancel = WinJS.Utilities.query('button#cancel', element)[0];

    // other setup, such as setting the caption

    return new WinJS.Promise(function (complete, error, progress) {
        ok.addEventListener('click', function () { complete(true); });
        cancel.addEventListener('click', function () { complete(false); });
    });
}


First, we use Windows Library for JavaScript to locate two button elements in the DOM. We want our promise to be fulfilled when either of these button are clicked. In this example, element represents the root DOM element for the confirmation dialog.

We return an instance of a new promise. The constructor for the promise takes a function. This function is executed by the new promise and it receives three arguments that are functions themselves. These functions are used to trigger the various states of the promise. In our example, we only care about complete.

We write handlers for the click events on both buttons and both handlers invoke complete. In fact, the only difference between them is the value passed to complete. This is the value that is passed to the parameter we named ok in our description of the confirmation module's interface.

The instance of the promise is returned as soon as show is invoked. However, it is not fulfilled until one of the two buttons is clicked. It's also interesting to note that there is nothing inherently asynchronous in the Windows Library for JavaScript implementation of promises. Rather, the asynchronicity is a product of something else. In our example, it's the DOM raising an event when the button is clicked, or our imaginary web service library invoking a callback that fulfills the promise.

Advanced composition of promises

Windows Library for JavaScript allows for more advanced composition of promises as well. Consider a scenario where you need to aggregate the results from several web services. In addition, you'd like to display a loading animation when the operation begins and remove the animation only after all of the results have been loaded. This is easily accomplished in Windows Library for JavaScript.



function loadData() {
    var urls = [
        'http://www.cohowinery.com/',
        'http://www.fourthcoffee.com/',
        'http://www.wingtiptoys.com'
        ];
        
    // begin playing an animation here

    var promises = urls.map(function (url) {
        return myWebService.get(url);
    });

    WinJS.Promise.join(promises).then(function () {
        // end the animation here
    });
}


First we create an array of URLs to query with our library. Next, we begin playing our loading animation. Then we use Array's map function to project the array of URLs into an array of promises. The assumption here is that get returns a promise. Finally, we use the WinJS.Promise.join function which accepts an array of promises and returns a new promise that is fulfilled after all of the input promises have completed. Inside the completion function of this aggregated promise is where we end our loading animation.

This is just one example of advanced composition of promises. Refer to the API documentation for WinJS.Promise for additional ways to use promises.

Summary

Asynchronous programming is necessary for both JavaScript in general and for interacting with Windows Runtime libraries. It can be difficult to read and debug asynchronous code once it reaches a certain level of complexity. Windows Library for JavaScript mitigates these difficulties with its implementation of promises. Promises allow developers to compose asynchronous apps using a predictable and consistent API.

 

 

Build date: 9/7/2011

Did you find this helpful?
(1500 characters remaining)
Community Additions ADD
possible leak of listeners?
wouldn't this code attach the listeners to the buttons each time show() is called? function show(caption) { var ok = WinJS.Utilities.query('button#ok', element)[0], cancel = WinJS.Utilities.query('button#cancel', element)[0]; return new WinJS.Promise(function (complete, error, progress) { ok.addEventListener('click', function () { complete(true); }); cancel.addEventListener('click', function () { complete(false); }); }); } Shouldn't the listeners be removed after the promise is gone?
11/17/2011