There are multiple ways to handle asynchronous code in JavaScript today. In this post, we’ll explore the three most popular async options by making an HTTP call to the jsonplaceholder API.
Call me, maybe.
Functions are first-class in JavaScript. This means you can pass functions around as arguments, and return functions from functions. Handy. When JavaScript was first released, callbacks were the standard approach for handling asynchronous operations. Callbacks are conceptually simple — you pass a function as a parameter that should be called later (when the async code completes).
In this example, I’m passing a callback to getPosts. Inside getPosts, I’m using XMLHttpRequest to make an HTTP call.
getPosts(function(error, posts) { if (error) { console.log(error); } else { console.log(posts.length); } }); function getPosts(callback) { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { callback(null, xhr.response); } } xhr.open("GET", "https://jsonplaceholder.typicode.com/posts"); try { xhr.send(); } catch (error) { callback(error); } }
I’m using Node’s callback style above, which involves specifying an error handler for the first parameter of the callback function (line 1). When the HTTP request completes, the callback is called on line 14. If an error occurs, an error is passed to the callback on line 21.
Promise: A guarantee to deliver later.
A promise is an object that represents the eventual completion of an async operation, and its resulting value. Today, promises are the most popular asynchronous approach. Promises are also the foundation for async/await (discussed below), so they remain well worth mastering.
Many libraries offer promise-based APIs such as Axios for HTTP calls, and native browser fetch. In this example, I’m using fetch to make an HTTP call instead of XMLHttpRequest because fetch provides a simple promise-based API for making asynchronous HTTP calls. Fetch is built into modern browsers.
getPosts().then( posts => { console.log(posts.length); }); function getPosts() { return fetch("https://jsonplaceholder.typicode.com/posts") .then(response => { return response.json(); }) .catch( error => console.log(error)); }
By convention, you handle a completed promise within a function called then(line 7). So, I return the promise that fetch supplies on line 6, and handle the resolved promise inside then on line 8. Finally, I handle any errors using .catch, also part of the promise spec.
Await your turn.
Async/await is a new approach that was added to JavaScript in ES2017. It actually uses promises behind the scenes, so you can think of async/await as syntactic sugar over promises. Thus, you still need to understand promises to use async/await. Like promises, async/await is non-blocking.
Async/await is often preferable over promises and callbacks because it makes async code look more like synchronous code:
etPosts().then(posts => { console.log(posts.length); }); async function getPosts() { try { const response = await fetch("https://jsonplaceholder.typicode.com/posts"); return response.json(); } catch(error) { console.log(error); } }
Asynchronous functions are declared with the async keyword (line 5). On line 7, I declare an async operation using the await keyword. The function will pause at the await keyword until the async call completes.
Async functions return a promise, so you still use promises to handle the response, as you can see on line 1 above. And you don’t use the await keyword on the return statement on line 8 because an async function’s return value is automatically wrapped in promise.resolve.
Recent versions of Node have async/await support built-in. Modern browsers support it too, but you’ll want to transpile using something like Babel or TypeScript for Internet Explorer support.
For more examples of the advantages above, read here.
[adinserter block=”33″]
One can also use raw iterators and generators, or interesting alternative libraries like RxJS, but promises and async/await are sufficiently powerful for most use cases.
Modern async approaches like Async/await are handy, but you still need to understand promises to use them. Favor async/await over promises and callbacks since the result is typically easier to read and maintain.
Want to toy around with this more? Here’s a GitHub repo you can clone and run locally.
Need to monitor your javascript applications? Use Stackify Retrace and its real user monitoring capabilities.
If you would like to be a guest contributor to the Stackify blog please reach out to stackify@stackify.com