Metro: Promises
- by Stephen.Walther
The goal of this blog entry is to describe the Promise class in the WinJS library. You can use promises whenever you need to perform an asynchronous operation such as retrieving data from a remote website or a file from the file system. Promises are used extensively in the WinJS library.
Asynchronous Programming
Some code executes immediately, some code requires time to complete or might never complete at all. For example, retrieving the value of a local variable is an immediate operation. Retrieving data from a remote website takes longer or might not complete at all.
When an operation might take a long time to complete, you should write your code so that it executes asynchronously. Instead of waiting for an operation to complete, you should start the operation and then do something else until you receive a signal that the operation is complete.
An analogy. Some telephone customer service lines require you to wait on hold – listening to really bad music – until a customer service representative is available. This is synchronous programming and very wasteful of your time.
Some newer customer service lines enable you to enter your telephone number so the customer service representative can call you back when a customer representative becomes available. This approach is much less wasteful of your time because you can do useful things while waiting for the callback.
There are several patterns that you can use to write code which executes asynchronously. The most popular pattern in JavaScript is the callback pattern. When you call a function which might take a long time to return a result, you pass a callback function to the function.
For example, the following code (which uses jQuery) includes a function named getFlickrPhotos which returns photos from the Flickr website which match a set of tags (such as “dog” and “funny”):
function getFlickrPhotos(tags, callback) {
$.getJSON(
"http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?",
{
tags: tags,
tagmode: "all",
format: "json"
},
function (data) {
if (callback) {
callback(data.items);
}
}
);
}
getFlickrPhotos("funny, dogs", function(data) {
$.each(data, function(index, item) {
console.log(item);
});
});
The getFlickr() function includes a callback parameter. When you call the getFlickr() function, you pass a function to the callback parameter which gets executed when the getFlicker() function finishes retrieving the list of photos from the Flickr web service. In the code above, the callback function simply iterates through the results and writes each result to the console.
Using callbacks is a natural way to perform asynchronous programming with JavaScript. Instead of waiting for an operation to complete, sitting there and listening to really bad music, you can get a callback when the operation is complete.
Using Promises
The CommonJS website defines a promise like this (http://wiki.commonjs.org/wiki/Promises):
“Promises provide a well-defined interface for interacting with an object that represents the result of an action that is performed asynchronously, and may or may not be finished at any given point in time. By utilizing a standard interface, different components can return promises for asynchronous actions and consumers can utilize the promises in a predictable manner.”
A promise provides a standard pattern for specifying callbacks. In the WinJS library, when you create a promise, you can specify three callbacks: a complete callback, a failure callback, and a progress callback.
Promises are used extensively in the WinJS library. The methods in the animation library, the control library, and the binding library all use promises.
For example, the xhr() method included in the WinJS base library returns a promise. The xhr() method wraps calls to the standard XmlHttpRequest object in a promise. The following code illustrates how you can use the xhr() method to perform an Ajax request which retrieves a file named Photos.txt:
var options = {
url: "/data/photos.txt"
};
WinJS.xhr(options).then(
function (xmlHttpRequest) {
console.log("success");
var data = JSON.parse(xmlHttpRequest.responseText);
console.log(data);
},
function(xmlHttpRequest) {
console.log("fail");
},
function(xmlHttpRequest) {
console.log("progress");
}
)
The WinJS.xhr() method returns a promise. The Promise class includes a then() method which accepts three callback functions: a complete callback, an error callback, and a progress callback:
Promise.then(completeCallback, errorCallback, progressCallback)
In the code above, three anonymous functions are passed to the then() method. The three callbacks simply write a message to the JavaScript Console. The complete callback also dumps all of the data retrieved from the photos.txt file.
Creating Promises
You can create your own promises by creating a new instance of the Promise class. The constructor for the Promise class requires a function which accepts three parameters: a complete, error, and progress function parameter.
For example, the code below illustrates how you can create a method named wait10Seconds() which returns a promise. The progress function is called every second and the complete function is not called until 10 seconds have passed:
(function () {
"use strict";
var app = WinJS.Application;
function wait10Seconds() {
return new WinJS.Promise(function (complete, error, progress) {
var seconds = 0;
var intervalId = window.setInterval(function () {
seconds++;
progress(seconds);
if (seconds > 9) {
window.clearInterval(intervalId);
complete();
}
}, 1000);
});
}
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
wait10Seconds().then(
function () { console.log("complete") },
function () { console.log("error") },
function (seconds) { console.log("progress:" + seconds) }
);
}
}
app.start();
})();
All of the work happens in the constructor function for the promise. The window.setInterval() method is used to execute code every second. Every second, the progress() callback method is called. If more than 10 seconds have passed then the complete() callback method is called and the clearInterval() method is called.
When you execute the code above, you can see the output in the Visual Studio JavaScript Console.
Creating a Timeout Promise
In the previous section, we created a custom Promise which uses the window.setInterval() method to complete the promise after 10 seconds. We really did not need to create a custom promise because the Promise class already includes a static method for returning promises which complete after a certain interval.
The code below illustrates how you can use the timeout() method. The timeout() method returns a promise which completes after a certain number of milliseconds.
WinJS.Promise.timeout(3000).then(
function(){console.log("complete")},
function(){console.log("error")},
function(){console.log("progress")}
);
In the code above, the Promise completes after 3 seconds (3000 milliseconds). The Promise returned by the timeout() method does not support progress events. Therefore, the only message written to the console is the message “complete” after 10 seconds.
Canceling Promises
Some promises, but not all, support cancellation. When you cancel a promise, the promise’s error callback is executed.
For example, the following code uses the WinJS.xhr() method to perform an Ajax request. However, immediately after the Ajax request is made, the request is cancelled.
// Specify Ajax request options
var options = {
url: "/data/photos.txt"
};
// Make the Ajax request
var request = WinJS.xhr(options).then(
function (xmlHttpRequest) {
console.log("success");
},
function (xmlHttpRequest) {
console.log("fail");
},
function (xmlHttpRequest) {
console.log("progress");
}
);
// Cancel the Ajax request
request.cancel();
When you run the code above, the message “fail” is written to the Visual Studio JavaScript Console.
Composing Promises
You can build promises out of other promises. In other words, you can compose promises.
There are two static methods of the Promise class which you can use to compose promises: the join() method and the any() method. When you join promises, a promise is complete when all of the joined promises are complete. When you use the any() method, a promise is complete when any of the promises complete.
The following code illustrates how to use the join() method. A new promise is created out of two timeout promises. The new promise does not complete until both of the timeout promises complete:
WinJS.Promise.join([WinJS.Promise.timeout(1000), WinJS.Promise.timeout(5000)])
.then(function () { console.log("complete"); });
The message “complete” will not be written to the JavaScript Console until both promises passed to the join() method completes. The message won’t be written for 5 seconds (5,000 milliseconds).
The any() method completes when any promise passed to the any() method completes:
WinJS.Promise.any([WinJS.Promise.timeout(1000), WinJS.Promise.timeout(5000)])
.then(function () { console.log("complete"); });
The code above writes the message “complete” to the JavaScript Console after 1 second (1,000 milliseconds). The message is written to the JavaScript console immediately after the first promise completes and before the second promise completes.
Summary
The goal of this blog entry was to describe WinJS promises. First, we discussed how promises enable you to easily write code which performs asynchronous actions. You learned how to use a promise when performing an Ajax request.
Next, we discussed how you can create your own promises. You learned how to create a new promise by creating a constructor function with complete, error, and progress parameters.
Finally, you learned about several advanced methods of promises. You learned how to use the timeout() method to create promises which complete after an interval of time. You also learned how to cancel promises and compose promises from other promises.