JavaScript is a single-threaded programming language and asynchronous programming is a foundational concept around which the language is built. There are three methods to deal with Asynchronous calls built into JavaScript as shown below:
- Callback Functions
- Promises and Promise Handling with .then() and .catch() method
- ES6+/ESNext style async functions using await
In this article, we will discuss how to deal with asynchronous calls in all of the above-mentioned ways. We will use the request module provided in Node.js to request JSON data from the website DOG CEO API which returns an URL link for a randomized dog picture in JSON format.
- Using a Callback function:
// Callback method
const request = require(
'request'
);
function
callback(res, body) {
console.log(JSON.parse(body));
}
function
getData(callback) {
// Using the request module to request
// data asynchronously
request(url, (err, res, body) => {
if
(err)
throw
err;
callback(res, body);
});
}
// Here we call the getData function
// with the callback function
getData(callback);
console.log(
'asynchronous is awesome'
);
Output:
asynchronous is awesome { message: 'https://images.dog.ceo/breeds/vizsla/n02100583_11450.jpg', status: 'success' }
Explanation: In this method, we request the API for JSON data and perform functions on the data using callback functions. For purposes of understanding, we simply log the data received to the console, but various other functions can be performed. This entire process happens asynchronously. The command
console.log('asynchronous is awesome');
although placed after the call to the function getData, is performed before getData is completed. - Using Promises with .then() and .catch() method:
// Promise with then catch
const promise =
new
Promise((resolve, reject) => {
request(url, (err, res, body) => {
if
(err)
return
reject(err);
try
{
resolve(JSON.parse(body));
}
catch
(error) {
reject(error);
}
});
});
promise.then((data) => {
console.log(data);
})
.
catch
((err)=>{
console.error(err);
});
console.log(
'asynchronous is awesome'
);
Output:
asynchronous is awesome { message: 'https://images.dog.ceo/breeds/pointer-germanlonghair/hans2.jpg', status: 'success' }
Explanation: The request module does not natively return promises. Hence we use the promise constructor to wrap the possible response received from the HTTP request into a native promise. This promise is then handled with .then() and .catch() method. The advantages of using this method are manifold. Firstly, resolving promises with .then() and .catch() method is the best practice for error handling and helps us to write code with an error first mindset. Secondly, JavaScript modules are adapting to support promises and are discarding the older callback style error handling. Newer modules like Axios, fetch, etc only support promises. Again, Promises are a relatively newer way of writing asynchronous code. This is proved by the fact that the command
console.log('asynchronous is awesome');
although placed after the call to the promise handler, is performed before the promise is completed. - ES6+/ESNext style async functions using await:
const getData = async () => {
let data = await
new
Promise((resolve, reject) => {
request(url, (err, res, body) => {
if
(err)
return
reject(err);
try
{
resolve(JSON.parse(body));
}
catch
(error) {
reject(error);
}
});
});
try
{
console.log(data);
}
catch
(err){
console.error(err);
}
}
getData();
console.log(
'asynchronous is best'
);
Output:
asynchronous is awesome { message: 'https://images.dog.ceo/breeds/sheepdog-shetland/n02105855_15196.jpg', status: 'success' }
Explanation: Async functions in Javascript allow us to stick to conventional programming strategies such as using for, while, and other methods that are otherwise synchronous in nature and cannot be used in Javascript. In this example, we wrap the possible response returned from the HTTP request in a promise. Since the promise takes a certain amount of time to either get resolved or rejected, we use the await keyword to wait for the promise to return a response. It is best practice to use a try()/catch() sequence to handle the response received from the promise after using await, to help us handle errors if any. Although synchronous code can be written in the try()/catch() sequence, the async function executes asynchronously, which is proved by the fact that the command
console.log('asynchronous is awesome');
although placed after the call to the function getData, is performed before the async function is completed.