Promisifying your XMLHttpRequest
I come from a Java background and when working with callbacks I often feel they are cumbersome. Working with promises feels far more logical for me. Callbacks are messy and they have a nasty side effect of obfuscating code. Promises on the other hand provide a way of creating a far cleaner code flow by making code look more like synchronous code.
If you’re not using the Fetch API then let’s take a web request using XMLHttpRequest
. Typically we setup out XMLHttpRequest
object and handle the response with callback functions. It’s often difficult to know when those
[gist]beb92d02ccea508e4f549d28fe2b3373[/gist]
Now calling the function is simply a matter of calling it together with the appropriate callbacks.
// call the requestFunction() by passing in
// a success and error callback.
request('http://localhost/user/:id', (response) => {
// do something
},
(error) => {
// do something
});
However, we sometimes need to make multiple requests on a page. especially when the application becomes more complex. Because of the asynchronous nature ofXMLHttpRequest we need to nest the second call to the http://localhost/user/:id/history endpoint. That’s not idea as it makes our code difficult to follow.
// call the requestFunction() by passing in
// a success and error callback.
request('http://localhost/user/:id', (response) => {
let user = JSON.parse(response.body);
// nest this call.
request(`http://localhost/user/${user.departmentId}/history`,
(response) => {
// do something
},
(error) => {
// do something
});
},
(error) => {
// do something
});
In contrast, promisifying the function means we can execute our code multiple times whilst waiting for the previous function to finish.
First, promisifying the XMLHttpRequest
object is done by wrapping the XMLHttpRequest
inside a promise object. We provide two callbacks. success and reject.
By using the async
keyword we are marking this function as asynchronous. When an async
function is called, it returns a Promise
which returns a value to be resolved in the calling code. When an exception is thrown the Promise
will be rejected with the thrown value.
[gist]c2839a39da3ec80a90b1c7cb278d90a2[/gist]
Calling our newly promisifiied function can be done in two ways. The first way is a bit easier to read but to be honest, I think it’s not too different to the first method presented with callbacks. However it does give a cleaner flow.
makeRequest('http://localhost/user/:id')
.then((user) => {
// do something
}
.catch((error) => {
// do something
});
However we are not far from where we started when it comes time to adding a nested request.
makeRequest('http://localhost/user/:id')
.then((user) => {
// nested call
makeRequest(<code>`http://localhost/user/${user.departmentId}/history</code>`) .then(userHistory => { console.log(userHistory); }) .catch((error) => { console.log(error); }); }) .catch((error) => { console.log(error); });
Thankfully there is a far cleaner, clearer and easier way. By using the await
keyword we do away with the nested calls, the callbacks and the mess. Inside a function marked with the async
keyword we can call themakeRequest()
function using the await
keyword like so:
let user = await makeRequest('http://localhost/user/:id');
let userHistory = await makeRequest(<code>`http://localhost/user/${user.departmentId}/history</code>`);
The await
keyword pauses the execution of the async
function and waits for the passed Promise
’s resolution and then resumes the async
function’s execution and returns the resolved value. You can’t use the await
keyword outside of an async
function. The purpose of async
/await
functions is to simplify the behaviour of using promises synchronously and to perform some behaviour on a group of Promises
.
So there you have it. A simply way to quickly improve code readability (and debugability) through employing a promise and the async
/ await
keywords. The (preferable) other alternative is to use the Fetch API which is promisified straight out of the box. Nice!
Assertions »