Making (JavaScript) Promises
5 min read.
When writing JavaScript code you often, if not always, end up writing something that is asynchronous. Whether it’s responding to File I/O, a AJAX request using or user interaction the standard method have been to use callback functions
. Writing a function that use callbacks can look something like the following
function fetchData(url, callback) {
if(typeof callback !== "function") {
return; //don't do anything if the callback is undefined
}
// code for fetching the data
callback(data);
}
var url = '...';
fetchData(url, function(data) {
// Callback function.
// do something with data when finished fetching it.
});
This works well and all up to a point when you start having multiple asynchronous functions that depends on the result of each other, which means we want to make them synchronously. As a worse case scenario, with callbacks, we could end up with something like the following
var url1 = '...';
var url2 = '...';
var url3 = '...';
var url4 = '...';
fetchData(url1, function(data1) {
// Handle data from first call
fetchData(url2, function(data2) {
// Handle data from second call
fetchData(url3, function(data3) {
// Handle data from third call
fetchData(url4, function(data4) {
// Handle data from fourth call
});
});
});
});
The problem with nestling functions like this is that over time the code will become harder and harder to understand. There are some ways you can refactor your code to avoid this phenomena called Callback Hell. One of them is using Promises.
The different forms of Promises
As of now, Promises comes in many different forms. However, in the next version of JavaScript, EcmaScript6, Promises will also be implemented natively.
Writing native promises
The basic structure for creating a promise is the following
var promise = new Promise(function(resolve, reject) {
// Do something async
if(/* everything with ok */) {
resolve(/* Message or data */);
} else {
reject(/* Message or data */);
}
});
The Constructor for creating a Promise takes a Callback function with two parameters. These are used to either successfully resolve the promise or reject the promise if the operation performed in the callback fails.
When the promise have been created it can be used as following
promise
.then(function success(data) {
// Handle the success.
}, function error(data) {
// Handle the error.
});
The promise contains a function called then
, this functions takes two callbacks, one for handling a success and one for handling an error.
The promise also contains a function called catch
. This function can be used as a substitute for using the error callback in the then
function.
Promises can also be chained together. It looks something like this
var promise1 = ...;
var promise2 = ...;
var promise3 = ...;
var promise4 = ...;
promise1
.then(function(){
return promise2;
})
.then(function(){
return promise3;
})
.then(function(){
return promise4;
})
.catch(function(error){
// Handle error.
});
Above we defined 4 promises that perform some kind of async tasks. Each task is then executed synchronously and we also use the catch
function to handle any errors that each promise could produce.
However, it doesn’t necessarily need to be promises that you chain in the then statements. then
can also be used for transforming data by just returning a new value in the callback function like this
var promise = new Promise(function(resolve){
resolve(1);
});
promise
.then(function(data){
return data + 1;
})
.then(function(data){
console.log(data) // 2
});
A real example
So how would you use this for real? well one example would be using it for AJAX requests fetching JSON
function fetch(url) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.open('GET', url);
request.onload = function() {
if (req.status == 200) {
resolve(request.response);
}
else {
reject(request.statusText);
}
};
request.onerror = function() {
reject("Network Error");
};
// Make the request
request.send();
});
}
var url = 'http://api.randomuser.me';
// fetching one user.
fetch(url)
.then(function(res){
return JSON.parse(res);
})
.then(function(jsonRes){
console.log("user: ", jsonRes);
})
.catch(function(err) {
console.log("error: ", err);
});
// fetching multiple users. using Promise.all
var user1 = fetch(url);
var user2 = fetch(url);
var user3 = fetch(url);
Promise.all([user1, user2, user3])
.then(function(responses){
//returns first user
console.log(JSON.parse(responses[0]));
})
.catch(function(err) {
console.log("error: ", err);
});
You can also find the code here.
Browser Support
As of writing this, the support for native Promises is the following
Desktop
For desktop browsers it’s currently supported in Chrome (ver. 32), Firefox (ver. 29), IE11, Opera (ver. 19) and Safari (ver. 7.1).
Mobile
On mobile the support is less extensive. Only Firefox Mobile (ver. 29), Chrome for Android (ver. 32) and Mobile Safari (ver. 8) supports native Promises.
If you want to support more browsers you need to either use one of the libraries mentioned earlier or use a polyfill.
More Reading
There are a lot of good articles out there that this post has taken inspirations from.
The libraries mentioned earlier, as well as the native implementation of Promises are based on the Promises/A+ spec.
There is quite good documentation and Browser Support Information available over at the Mozilla Developer Network.
Jake Archibald have written an extensive post on the subject.
Also, Last week the new Fetch API was introduced into Chrome (ver 40) that is a nice substitute for XMLHttpRequest
. Fetch also uses promises.