We'll go through the basics of Promises and callbacks, and demonstrate the simplicity of promisifying the old XMLHttpRequest API.
It is no longer news that new JavaScript development patterns since the evolution of ES6 have influenced how we approach certain things in the JavaScript ecosystem. For instance, the standard callback function syntax which has been a standard for years. However, a new alternative, Promises, is now a more preferred pattern of development.
Promises represent a common way to react to asynchronous operations in JavaScript. A good example of a Promise-based API is the new fetch
API provided by browsers. Not that you don’t already know this, but fetch
is a replacement for the older callback-based XMLHttpRequest
API, and, in this post, we are going to look at a few ways we can promisify some of our older APIs just for fun. 😃
Let’s start with simple callbacks. In my opinion, the most common browser API that we come across is the setTimeout()
API. It waits for a specified period of time and then executes a callback. A typical demonstration is this:
window.setTimeout(() => {
// do something after 1sec
}, 1000);
As expected, the function waits for a second and then executes. With that in mind, let’s go ahead and see how we can promisify it just to understand how Promises better clarifies asynchronous processes.
To define an A+ compliant Promise using the Promise constructor, we will pass in a function to the constructor. The function requires two arguments, resolve
and reject
, which are themselves callback functions under the hood. This is what we mean:
const promisifiedSetTimeout = (delay, value) =>
new Promise((resolve) => window.setTimeout(resolve, delay, value));
Notice that we did not use the
reject
callback here. Why? BecausesetTimeout()
does not provide any hooks for an error state. So instead, we passresolve
as the callback tosetTimeout()
.
This gives us a chainable setTimeout()
function. As much fun as it is to do stuff like this (promisify callbacks), I will advise you don’t get carried away. Many JavaScript APIs and most standard browser APIs are still heavily callback driven. This includes popular libraries like jQuery.
As a developer, this brings us to a bit of a crossroads, where we are caught between mixing two different styles of asynchronous code.
But who says we can’t just convert all our callbacks to promises? Let’s look at a more complex example and promisify the popular XMLHttpRequest
API. But before that, let’s demonstrate the functionality to understand it better.
The XMLHttpRequest
API was designed to fetch XML data over HTTP, hence the name. But today it can be used with protocols other than HTTP and it can fetch data not only in the form of XML, but also JSON, HTML or plain text. Here’s a quick example using the XMLHTTPRequest
to fetch a Github user:
function success(){
var data = JSON.parse(this.response);
console.log(data);
}
function error(err){
console.log('You have an error', err)
}
var request = new XMLHttpRequest();
request.onload = success;
request.onerror = error;
request.open('GET', 'https://api.github.com/users/kennypee');
request.send();
Here we have defined our request that we’ll use to retrieve data from the server asynchronously. The request returns a response from the server and stores it in the response
variable. We also converted the response into a JavaScript object using the JSON.parse()
method. In addition, we have defined two listeners to handle the success and error cases.
The progression here is, first, we open the request by passing the request method GET
and the request URL where we’ll fetch the data from, then we send the request with the call to send()
.
If our request is successful, we store the data from the server into our response
variable and convert it into an object and then log it to the console. If there is an error in our request, we log the error to the console with the error()
case handler.
This brings us back to our initial question, is there a cleaner and simpler way to do this? The answer is YES. We can use Fetch to simplify this process in modern JavaScript development. Fetch is an improvement on the XMLHttpRequest
API. The major difference between Fetch and XMLHttpRequest
is that the Fetch API uses Promises, hence avoiding callback hell.
The fetch()
function takes a compulsory argument, which is the path to the resource you want to fetch. The functions return a Promise in any case, whether success or error. It has dedicated handlers for either case. If the request is successful, the .then()
function will receive the response object from the server; if the request fails, the .catch()
function receives the error object.
If we wanted to achieve the same functionality we have on the previous example with Fetch, here’s what we’ll do:
fetch('https://api.github.com/users/kennypee')
.then(function(response)){
return response.json()
})
.then(function(data)){
console.log(data)
})
.catch(function(err)){
console.log('You have an error', err)
});
Here we have done the same thing we did with the XMLHttpRequest
but in a much simpler way. We’ve made use of the Fetch API to call to GitHub endpoint and fetch data about a user. When the promise is resolved, we get a Response
object in return.
But the response object here only has information about the response itself and not of the user we asked for. To get the data we desire, we had to pull it from the response body by calling the .json()
method on the response object to return a Promise, hence the need to chain another .then()
function for the data
.
Now that we have seen an undiluted XMLHttpRequest
API and demonstrated the Fetch
version of the same API, let’s try to promisify the XMLHttpRequest
API. This will be more of a mix of both the XMLHttpRequest
API and Fetch
. To promisify our previous example of the XMLHttpRequest
API, here are the steps we’ll take:
success
and error
arguments in the function signature.success()
, resolve the Promise.error()
, reject the Promise.With these modifications, we can promisify our XMLHTTPRequest
API to get the result below:
const fetch = (url, options = {method:'get'}) => new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.onload = resolve
request.onerror = reject;
request.open(options.method, url, true);
request.send();
});
This is a concise way of promisifying the XMLHttpRequest
API without having to rewrite the existing code. More so, we can always replace all the old callback functions with the new .then()
and .catch()
syntax wherever the need be.
It might also interest you to know that, since Node.js version 8, you can convert all your callbacks to Promises using a built-in module called the util
. All you need to do is call util.promisify()
to convert your callback to a Promise.
Not that you don’t know all these but I hope you had fun doing it all over again to reinforce the knowledge. We demonstrated the conversion of usual callbacks to Promises and more popular XMLHttpRequest to Fetch just for the fun of it. To learn more about Promises, feel free to check out this documentation.
Want to learn more about creating great web apps? It all starts out with Kendo UI - the complete UI component library that allows you to quickly build high-quality, responsive apps. It includes everything you need, from grids and charts to dropdowns and gauges.
Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.