Callbacks
In this example we are not using promises at all. What we are trying to achieve is the following: Get information about a destination airport via a webservice call, once that’s done, we’d like to retrieve weather inforamtion for the same destination.
The problem is that we have to nest these calls. And if we’d like to extract information out of the returned dataset that just adds complexity to the app.
Pyramid of doom
You can see in the below code snippet how the requests are nested. Imagine if we'd have 3 or 4 requests, each time would have to further nest our code.
'use strict';
var city = '';
var country = '';
var weather = '';
function go() {
var code = document.getElementById('iata').value;
var request = new XMLHttpRequest();
request.open('GET', 'http://airportapi-tpiros1.rhcloud.com/airport/IATA/' + code);
request.onload = function() {
if (request.status === 200) {
// parse & build up return values
var result = JSON.parse(request.response)[0];
city = result.municipality;
country = result.iso_country;
// we are resuing the values to make our second web service call
request.open('GET', 'http://api.openweathermap.org/data/2.5/weather?q=' + city + ',' + country);
request.onload = function() {
if (request.status === 200) {
// again, parse the values
var result = JSON.parse(request.response);
var celsius = (result.main.temp - 273.15).toPrecision(2); //Kelvin to Celsius
var description = result.weather[0].description;
weather = celsius + '°C and ' + description;
console.log('The weather in ' + city + ', ' + country + ' is ' + weather + '.');
} else {
console.log(request.statusText);
}
}
// this sends the second API call
request.send();
} else {
console.log(request.statusText);
}
}
// this sends the first API call
request.send();
// when this line is reached, we don't have the values fulfilled ... so we need to move it up
// console.log('The weather in ' + city + ', ' + country + ' is ' + weather + '.');
}
Use the above script in conjunction with this HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
html, input {
font-family: Georgia;
font-size: 32px;
font-weight: 700;
}
input[type=text] {
color: #aaa;
}
.btn {
color: #fff;
border: solid 1px #ccc;
border-radius: 16px;
padding: 5px;
background: -webkit-gradient(linear, left top, left bottom, from(#00adee), to(#0078a5));
background: -moz-linear-gradient(top, #00adee, #0078a5);
}
</style>
<script>
function go() {
console.log('%c You should not see this message. Plese uncomment a script line in the HTML source.', 'color: red;');
}
</script>
<script src="app.promise.js"></script>
<!-- <script src="app.nopromise.js"></script> -->
</head>
<body>
<div>
<p>IATA code: <input type="text" maxlength="3" size="3" id="iata"></p>
<p><input type="submit" class="btn" value="Get Information" onclick="go()"></p>
</div>
</body>
</html>
The above HTML code allows you to enter a three letter airport code (IATA code) and clicking the button will initiate the request to the web service specified in the previous JavaScript code snippet.
Using promises
Let's take a look at how can we rewrite our JavaScript code with promises so that we can get a chain of events happening:
// Native promise implementation
'use strict';
var city = '';
var country = '';
// this function returns a promise (resolves or rejects)
function getPromise(url) {
var promise = new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.onload = function() {
if (request.status === 200) {
resolve(request.response)
} else {
reject(new Error(request.statusText));
}
};
request.onerror = function() {
reject(new Error('Cannot get data'));
};
request.send();
});
return promise;
}
function go() {
var code = document.getElementById('iata').value;
var promise = new getPromise('http://airportapi-tpiros1.rhcloud.com/airport/IATA/' + code);
promise
.then(function(result) {
result = JSON.parse(result)[0];
city = result.municipality;
country = result.iso_country;
return getPromise('http://api.openweathermap.org/data/2.5/weather?q=' + city + ',' + country);
})
.then(function(result) {
result = JSON.parse(result);
var celsius = (result.main.temp - 273.15).toPrecision(2); //Kelvin to Celsius
var description = result.weather[0].description;
var weather = celsius + '°C and ' + description;
console.log('The weather in ' + city + ', ' + country + ' is ' + weather + '.');
});
}
Notice how we have a the go() function returning a promise. Promises can be chained using the then() method. This method also returns a promise and it takes two function arguments - a function that gets invoked when the promise is fulfilled and another one when the promise is rejected (not shown in the example).
In AngularJS some service do implement Promises natively - one such service is $http
. So all HTTP calls ($http.get()
, $http.post()
and so on all return a promise therefore you can call the .then() methods on them.
In light of this, the previous airport example could be rewritten using the following call:
$http
.get('http://airportapi-tpiros1.rhcloud.com/airport/IATA/' + vm.code)
.then(function(airportInfo) {
console.log(airportInfo[0].name + ' is an airport at ' + airportInfo[0].municipality
+ ', ' + airportInfo[0].iso_country);
});
In a future article we'll see how you can also make use of Promises in AngularJS via the resolve
property on Angular routes.