Asynchronous JavaScript – The Beginners Guide
Before you get started with this guide, you should have a fair knowledge of JavaScript fundamentals. At its base, JavaScript is a synchronous programming language. This means it is single-threaded; only one operation can be executed at a time.
To truly understand why async/await is important, we have to take a deep dive into JavaScript’s core concepts.
Here’s an example:
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
alert('You clicked me!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
By default, the code execution in JavaScript is synchronous. This means that each operation blocks are executed one after the other.
In this block, the lines are executed one after the other:
- We grab a reference to a <button> element that is already available in the DOM.
- We add a click event listener to it so that when the button is clicked:
While each operation is being processed, nothing else can happen — rendering is paused. This is because JavaScript is single-threaded. Only one thing can happen at a time, on a single main thread, and everything else is blocked until an operation completes.
Therefore, operations that typically take longer (e.g. network requests) will block any further execution. This is quite bad and will naturally lead to performance issues. Through features like the Event Loop, modern JavaScript found ways to execute code asynchronously. Thus, it can perform operations seemingly concurrently and not only sequentially.
What is Asynchronous JavaScript?
Functions running in parallel with other functions are called asynchronous
Asynchronous JavaScript programming makes it possible to express waiting for long-running actions without freezing the program during these actions.
JavaScript environments typically implement this style of programming using callbacks; functions that are called when an action completes. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap.
Understanding Async / Await
Since JavaScript can’t really multitask, we’ll need a way to handle long-running processes. Async/Await is a way to handle this type of time-based sequencing.
“async and await make promises easier to write”
async makes a function return a Promise
await makes a function wait for a Promise
An asynchronous JavaScript function can be created with the async
keyword before the function
name, or before parenthesis ()
when using the async arrow function. An async
function always returns a promise.
Promises in JavaScript are objects that can have multiple states . Promises do this because sometimes what we ask for isn’t available immediately, and we’ll need to be able to detect what state it is in. to really understand async/await
, you need to know how Promises work.
A Promise
exist in one of these states:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
- rejected: meaning that the operation failed.
Here is an example;
const getSomeTacos = new Promise((resolve, reject) => {
console.log("Initial state: Excuse me can I have some tacos");
resolve();
})
.then(() => {
console.log("Order some tacos");
})
.then(() => {
console.log("Here are your tacos");
})
.catch(err => {
console.error("Nope! No tacos for you.");
});
We stored a promise called getSomeTacos
, passing in the resolve and reject parameters. We tell the promise it is resolved, and that allows us to then console log two more times.
You can read more on promises.
To declare an async function, put async
at the beginning of your function declaration.
Like so:
async function greeting(hello) {
return hello;
}
Technically, we can call an async function just like a normal Promise, using a .then()
statement to make sure it evaluates to a value, and not just a Promise object.
async function greeting(hello) {
return hello;
}
greeting('Hi'); // Promise { ‘Hi’ } <-- evaluates to a Promise object
greeting('Hey')
.then(hello => console.log(hello)); // ‘Hey’ <-- evaluates to a fulfilled Promise
If we call greeting
by itself, then it’s going to evaluate to a pending Promise object, so we use .then()
to make sure the Promise is fulfilled.
The “await” keyword tells our code to wait for the return of a Promise before it continues executing our program.
Let’s try it out
async function makeCall() {
try {
const response = await axios.get('https://somewebsite.com');
console.log(response.data);
} catch(error) {
console.log(error);
}
};
So we’ve got an async function makeCall()
. We use the await
keyword to wait for our GET request to run. This means that the rest of our program does not continue until our web request has been processed. The axios library is also built on Promises; axios requests return Promises.
But if a function does not inherently return a Promise, await
turns it into one that does.
The await
keyword basically injects the resolve
of a Promise that would otherwise need to be finished/fulfilled with a .then()
.
Here, we’re storing that resolve in a variable because we want to do something with it– namely, grab the data from that response object and then console log it.
We will always run into errors as developers, so here, we handled the error using a try…catch statement. We placed the body of our main call in a try
block, and follow that up with a catch
block
Resources
Here are some resources that can help you, if you need more insight.
- Promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://www.w3schools.com/js/js_async.asp
Summary
Async/await functions help you write asynchronous operations effectively. When an async function is declared, you can use the “await” keyword to wait for operations to resolve. The await keyword must be used with a function that returns a Promise.