JavaScript has grown to become one of the most popular languages in the world today. It is a single-threaded language which means only one thing can be done at a time. This had previously been a limitation until asynchronous JavaScript — using promises and async/await — was added to JavaScript.
In this article, we will learn how to use asynchronous JavaScript more effectively.
JavaScript is a single-threaded language, meaning it only allows one logic to be performed at a time, because of this you can’t perform complex long functions that will block JavaScript’s main thread. To solve this, callbacks — which are functions passed into other functions as an argument to be executed later — were used to perform asynchronous functions. Using async JavaScript, you can perform large functions without blocking the main thread of JavaScript.
To better understand this, let’s look at what we mean by synchronous and asynchronous JavaScript.
Synchronous JavaScript as the name implies, means in a sequence, or an order. Here, every function or program is done in a sequence, each waiting for the first function to execute before it executes the next, synchronous code goes from top to bottom.
To better understand synchronous JavaScript, let’s look at the code below:
let a = 5; let b = 10; console.log(a); console.log(b);
And here is the result:
Here, the JavaScript engine executes the first one in the equation, which in this case is 5 and then goes down to execute the second line of code, printing 10 to the console. If we add any other line of code, the JavaScript engine executes it based on the position we add it, this is what synchronous JavaScript entails, a sequential way of executing code.
Now, we have an idea on how synchronous JavaScript works, let’s talk about asynchronous JavaScript. To explain this, let’s look at the code below:
console.log("Hello."); setTimeout(function() { console.log("Goodbye!"); }, 3000); console.log("Hello again!");
Unlike our other example, JavaScript engine won’t execute the code above synchronously. Let’s take a look at the output below:
In the code, we logged Hello
to our console, next we wrote a function that will log Goodbye
to our console after three seconds and the last part of our code logs Hello again
to our console. Here, the JavaScript engine goes through the first function and executes it, printing Hello
to the console, going to the next function, it sees the setTimeout
function and instead of waiting for three seconds to print the function, it goes to the last function and executes it, printing Hello again
, waits for three seconds, and then executes the second function.
So with asynchronous JavaScript, the JavaScript doesn’t wait for responses when executing a function, instead it continues with executing other functions. Let’s look at ways of executing asynchronous JavaScript .
There are two ways of writing asynchronous code in JavaScript, promises and async/await.
A promise only passes if a certain criteria is true. With JavaScript promises, we can defer a code execution until an asynchronous request is completed, this way other functions can keep running without blocking the thread.
Promises are a new way of writing asynchronous JavaScript, its usually an object with three main states, which includes:
To better understand this, let’s create a promise below:
const hungry = true; const eat = new Promise(function(resolve, reject) { if (hungry) { const fastfood = { activity: 'Cook noodles', location: 'Market Square' }; resolve(fastfood) } else { reject(new Error('Not hungry')) } });
In the code above, if hungry
is true, resolve the promise returning the data with a fastfood
with an activity that says Cook noodles
, else return an error object that says Not hungry
.
Let’s push this further and use the promise we initialized above, we can chain .then()
and a .catch()
method to our Promise below:
const willEat = function() { eat .then(function(hungry) { console.log('Going to eat noodles!') console.log(hungry) }) .catch(function(error) { console.log(error.message) }) } willEat();
In the code above, we created a new function called willEat()
with a promise of eat
, next we used the .then()
to add a function that will contain the resolve for our promise. We then added a .catch()
method to return the error message in our promise.
Since the hungry value is true, when we call our willEat()
function, we should get the results below:
Going to eat noodles! { activity: 'Cook noodles', location: 'Market square' }
If we change the value of hungry to false, our promise will display the status for a failed promise which in our case will be not hungry
. We can push our promises further by creating a new promise that’d take parameters from our earlier examples:
const foodTour = function(fastfood) { return new Promise(function(resolve, reject) { const response = `I'm going on a food tour at ${fastfood.location`; resolve(response) }); }
In the code above, we created a new promise called foodTour
that takes the fastfood
value from earlier example, and resolves a response with a template string on the fastfood
location in our earlier example.
Async/await was added in the (ES2017+) release, it is syntactic sugar that makes it easier to write promises in JavaScript. Async/await helps you write synchronous-looking JavaScript code that works asynchronously.
An async function returns a promise, if the functions returns a value, the promise is resolved with the value, but if the async function throws an error, the promise is rejected with that value. Let’s create a simple async function below:
async function favoriteDrink() { return 'Monster energy drink' }
Here, we are declaring a function called favoriteDrink()
which returns Monster energy drink
. If a promise is rejected in an async function, it displays a rejected method similar to this:
async function() { throw 3; }
Await is in an async function to ensure that all promises that are returned in the function are synchronized. With async/await, there’s no use of callbacks. try
and catch
methods are also used to get rejection values of async functions. Let’s create an async/await function wrapped inside of a try…catch
method using our earlier examples:
async function willEat() { try { let fastfood = await eat; let response = await foodTour(fastfood); console.log(response); } catch(error) { console.log(error.message); } } willEat();
Here we converted our earlier example to use async/await wrapped in try…catch
method, we logged the response as our earlier example, which returns the string I'm going on a food tour at Market Square
.
Recently in JavaScript, the fetch()
API has been used for API requests to URLs. Before this, requests were made using XMLHttpRequest. With ES2017+
, using fetch API and async/await, you can make asynchronous requests to URL endpoints, first you’d need to define the function as an async function and await the response in json
and then return your data. To better explain this, let’s look at the code below:
async function getJobAsync() { let response = await fetch(`https://cors-anywhere.herokuapp.com/https://jobs.github.com/positions.json`); let data = await response.json() return data; } getJobAsync('jobPositionHere') .then(data => console.log(data));
In the code above, we wrote an async function getJobAsync()
that makes a fetch request to an external URL, next we awaited the response
in a json
format and then returned the data once the request is resolved. This is how to make an asynchronous request using asynchronous JavaScript. Let’s look at the result of the function in the image below.
Next, we are going to look at how to return the response from an asynchronous API call.
There are many ways to return the response from an async call in JavaScript, callbacks, and promises. Let’s say you are making an asynchronous call and you want the result of the call to be from the function, this can be done using async/await, let’s explain this further in the code below:
const getResult = async (request) => { let response = await new Promise((resolve, reject) => { request((err, res, body) => { if (err) return reject(err); try{ resolve(JSON.parse(body)); } catch(error) { reject(error); } }); }); try{ console.log(response); } catch(err){ console.error(err); } } getResult(); console.log('This is how to return async JavaScript');
In the code block above, we are wrapping the response from the request in a promise, then await it while it gets resolved or rejected, and also wait for the promise to return a response. In JavaScript operations, it’s advised to wrap your code in a try…catch
method in order to handle errors that might be in our function. Lastly, we call the function at the end of the program and log the message This is how to return async JavaScript
in our console, this is how we respond to asynchronous calls in JavaScript, callbacks or async/await.
In this article, we learned what asynchronous JavaScript is and how to write asynchronous JavaScript using promises and async/await. We’ve also seen how to send requests using the fetch API and async/await and how to return a response to asynchronous calls. You can read more on asynchronous JavaScript here.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Understanding (and effectively using) asynchronous JavaScript"
Useful Information, keep up the good work