Understanding Async Await in JavaScript
One important part of JavaScript programming is handling asynchronous tasks such as a Rest API call to fetch data from a backend. Let's see what are the different approaches to deal with async tasks in JavaScript from the start:
Callbacks Promises Async and Await #Callbacks The first solution to deal with async tasks in JavaScript was using callback functions. In JavaScript functions are objects and thus functions can take functions as arguments, and functions can be returned from other functions. Such functions are called Higher Order Functions. A function that is passed as an argument is called a callback function and can executed after the current function has finished executing, therefore called as a callback.
example of a callback function:
example of a callback function:
const logName = function (name) { console.log(Hello ${name}
); }
const makeName = function (firstName, lastName, callback) { const name = ${firstName} ${lastName}
;
callback(name); }
makeName('James', 'Bond', logName);
// output // Hello James Bond
using callback for async task:
const AsyncRequest = require(‘request’);
function handleResponse(error, response){ if(error){ console.log(error); } else { console.log(response); } }
AsyncRequest('codewhoop.com/users', handleResponse);
This is a very simple example of callback function where handleResponse is used as a callback function when we get the response or error from our AsyncRequest, but in real world applications working with callbacks to handle async tasks wouldn’t scale because of an issue called Callback Hell .
#Promises A promise represents the eventual result of an asynchronous operation which may be success or faliure. Using promises is a better way to work with async tasks as promises have well defined states and we dont have to handle all that ourself. It also provides a cleaner syntax and makes code more readable. A Promise can have one these states:
Pending - still waiting to be fulfilled or rejected. Fulfilled - the task completed successfully. Rejected - task failed. Let's convert the above example into a promise by assuming that a promise object is returned by the request and we will hande the response with .then(). We can create a custom promise using the new Promise constructor, but here we will assume that our AsyncRequest returns a promise.
const AsyncRequest = require(‘request’);
AsyncRequest('https://www.codewhoop.com/users').then((response).then((response)) => { console.log(response); }).catch((error) => { console.log(error); }); So here we can see that by using .then() we can handle what to do when we get the data as well as catch the error in a cleaner and more logical way, this also saves us from passing functions around and simplifies the logic.
#Async and Await Now using async and await we can implement async tasks in a similar way as we write our synchronous code. But what happens here is if we use async before a function then JavaScript knows that this function is going to handle some async task and using await before that task we declare that this is the async task which has to finish before execuing further. We can only use await inside a function if we have theasync keyword and a async function always returns a promise. So we can modify the above example as:
const AsyncRequest = require(‘request’);
async function handleResponse(){ const response = await AsyncRequest('https://www.codewhoop.com/users'); console.log(response); }
handleResponse(); #Error handling in Async - Await To handle error in this approach we use try and catch as follows:
const AsyncRequest = require(‘request’);
async function handleResponse(){ try{ const response = await AsyncRequest('https://www.codewhoop.com/users'); console.log(response); } catch (error){ console.log(error); } }
handleResponse(); As we can see using async and await our code looks more synchronous. Although in the background it still works based on the promise logic but it provides us a much cleaner looking and easy to understand code.