JavaScript Promises are a feature of ECMAScript 6 (aka ES6), the new version of JavaScript scheduled for release in the not too distant future. However, the feature has already been written, in some form, into a number of browsers including IE12, Chrome (32+) and Firefox (25+).
But what exactly are Promises in JavaScript, and why should we be interested in them?
Put simply, ‘Promise
’ is a JavaScript class (constructor function) which helps us to write code involving asynchronous operations.
To help us understand the concept of Promise
objects (instances of the Promise
class), it is helpful to think of them as indeed representing ‘promises’ in the traditional sense of the word. So then, who is it that is making these promises, to whom are they being made, and what are they promising?
Before answering these questions, let's first take a step back and remind ourselves what asynchronous programming is and why it is important in JavaScript.
If you think of an asynchronous operation in JavaScript (or indeed any language), it typically consists of one function calling another function without having to wait for its return (that is, without being blocked). This capability is especially important in JavaScript, a single-threaded language in which, and in web pages that single thread is also responsible for handling user input. If your JavaScript code needs to call some function which takes a while to complete, then your browser will freeze until it is done, unless of course you can call it asynchronously.
To meet this need, JavaScript programmers have traditionally used callbacks to implement asynchronous programming. One function which has been used a great deal in recent years is the jQuery.ajax()
function, which performs an asynchronous HTTP request. Since jQuery v1.5, you can make use of Promises when calling the jQuery.ajax()
function (more on this later), however prior to this it was necessary to use callbacks to achieve asynchronicity.
For example:
$.ajax({
type: 'POST',
url: myUrl,
data: myData,
success: function(data) {
alert(data);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus); alert("Error: " + errorThrown);
}
});
Here, we pass success and error functions as callback arguments, to be executed if the operation succeeds or fails respectively.
At this point, we can reconsider the ‘promise
’ that is being made here.
As is so often the case in programming, it is helpful to think of things in terms of a client-service relationship. Think of the calling code here as the client, and the code responsible for performing the asynchronous operation as the service (in this case, a service being offered an HTTP server). When we send our request to the service, an implicit promise is made to us (the client) by the service. It promises to fulfil our request, and if it keeps its promise the success callback function will be called. However, promises are sometimes broken, and if the service breaks its promise (perhaps the server database is down), then the error
callback function is called. To summarise then:
In a typical asynchronous programming scenario, a ‘client’ sends a request to a ‘service’, and the service promises to fulfil the request. The client can adjust its behaviour depending on whether the service keeps or breaks its promise.
Hopefully, you can now begin to see how a kind of promise is being made here, by the service, to the client. Now let’s take a look at an example using the Promise
class:
function doSomething(){
return Math.floor(Math.random()*11) > 5;
}
var promiseMeThis = new Promise(
function(resolve, reject) {
window.setTimeout(function() {
var promiseKept = doSomething();
if(promiseKept)
resolve();
else
reject();
}, 3000);
}
).then(function(){
alert('promise fulfilled!');
}).catch(function(){
alert('promise broken (rejected)!');
});
The main point to note here is how we are chaining function calls together, rather than passing the actual callback functions into the Promise
constructor. The then()
function allows us to set a ‘success’ callback, and the catch()
function allows us to set an ‘error’ callback. Each of these functions returns a Promise
object, thus allowing us to chain function calls together in the manner shown.
But why? How is this approach any better than the old-fashioned way?
In a simple scenario, there is not much difference, however as our asynchronous requirements become more complex, the Promise approach offers much simpler solutions.
Consider an example where we need to call a function asynchronously, and then if that succeeds call another, and then another, and so on. In the old world, the code might look something like this:
$.ajax({
type: 'POST',
url: myUrl,
data: data1,
success: function (data2) {
$.ajax({
type: 'POST',
url: myUrl2,
data: data2,
success: function (data3) {
$.ajax({
type: 'POST',
url: myUrl3,
data: data3,
success: function (data4) {
alert(data4);
},
error:
function (XMLHttpRequest, textStatus,errorThrown){
alert("Status: " + textStatus);
alert("Error: " + errorThrown);
}
});
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus);
alert("Error: " + errorThrown);
}
});
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus);
alert("Error: " + errorThrown);
}
});
Not very readable, is it? And this is with only 3 consecutive ajax calls. And what are you supposed to do if you only want one of your error callback functions to be called? There is no obvious or simple solution without Promises.
Compare this to a similar scenario using Promises:
function doSomething() {
return Math.floor(Math.random() * 11) > 1;
}
var promiseMeThis = new Promise(
function (resolve, reject) {
window.setTimeout(function () {
var promiseKept = doSomething();
if (promiseKept) resolve();
else reject();
}, 1000);
}).then(function () {
window.setTimeout(function () {
var promiseKept = doSomething();
if (promiseKept) resolve();
else reject();
}, 1000);
}).then(function () {
window.setTimeout(function () {
var promiseKept = doSomething();
if (promiseKept) resolve();
else reject();
}, 1000);
}).then(function () {
alert('promise fulfilled!');
}).catch (function () {
alert('promise broken (rejected)!');
});
Not only is this much clearer about what it is trying to achieve, but the error callback function will only be called once if any of the asynchronous operations fail.
I mentioned earlier that since jQuery v1.5 we can in fact make use of Promises when calling the .ajax()
function. This is because this function now returns an object which implements the Promise
interface, and we can achieve its behaviour by using functions such as .done()
and .fail()
, which as you might have guessed return Promise
objects themselves (i.e. objects which implement the Promise
interface).
So we can now write the .ajax()
example from above as follows:
$.ajax({
type: 'POST',
url: myUrl,
data: data1
}).done(function(data2){
$.ajax({
type: 'POST',
url: myUrl2,
data: data2
});
}).done(function(data3){
$.ajax({
type: 'POST',
url: myUrl3,
data: data3
});
}).done(function(data4){
alert(data4);
}).fail(function (XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus);
alert("Error: " + errorThrown);
});
Much nicer.
The post JavaScript Promises appeared first on The Proactive Programmer.