Most of the time, we code in a linear way, with things happening one after the other. However, in some special cases, some things may happen in parallel, or not sequentially. In fact, in those cases, we may want some asynchronous JavaScript. In this JavaScript tutorial, we will see how the async and await constructs help us do that in an elegant and simple manner.
This JavaScript tutorial goes into some mid-level concepts. Thus, you should have already a good understanding of JavaScript to better enjoy the reading. If you don’t, you may wish to head first to this Getting Started with JS guide. Additionally, this guide to HTTP Calls using JavaScript can also help you as well on a nice use-case.
What Does “Asynchronous” Mean?
In JavaScript like any other programming language, some operations may take time as they rely on external services. It may be the stream of a video, the call to a web API, or even storing large pieces of data onto a disk – executing some specific code takes time.
We have two ways to go about these operations:
- In synchronous mode, we execute the long-lasting piece of code together with the rest of the code. Effectively, we just run it as part of the rest of the program, and when we get there we will simply wait for it to finish.
- The opposite of that is asynchronous mode, where we spin-off the bulky piece of code so that it can run separately from the main code. It will run separately, in parallel.
For many different situations, asynchronous is the way to go. To give you an example, if you are calling an API you are just waiting for the server to respond. There is no need to halt the entire execution of the program while waiting for that. Rather, you want to just wait for the API to respond while your main thread does something else. Hopefully, something productive.
With this approach, we can easily create complex programs that execute rich tasks easily. Async and Await are two special keywords that work together and allow us to do just that.
Introducing Async-Await in JS
Event-Driven Programming
So far we looked at the benefit of spinning-off the execution of a bulky part of the application, asynchronously. This is wonderful, but by doing just that we do not enhance our programming experience by much.
In fact, we always want to do something when the bulky operation finishes. If we call an API, once we finish we want to get the data from it and do something with them. In other words, we need to patiently wait for the bulky operation to finish.
Obviously, JavaScript offers a possibility to do so. This is thanks to the concept of promises.
Promises
A Promise is a particular piece of code that allows asynchronous execution. Technically, it is a function that will settle in the future, but this fancy definition means something way simpler. You just create the logic of your Promise to trigger it at a later stage. When you do trigger it, it can take some time to execute. However, code will not stop to wait for it, it will spin it off in a separate thread, asynchronously.
You can either define your own promises, or use (trigger) existing ones. For this article, we will only see how to trigger an existing promise. However, in case you want to go the extra mile, check here how to create your own Promise.
A promise is a thenable object because it has a .then()
method. This method wants to know a function as a parameter. The function that you will include, in fact, is the one the promise will launch when the promise settles.
promise.then(function () {
// Some code to execute after settling
});
To make things simple, we can imagine this process as “Once the promise settles, execute the then function”. Or, to give you an even clearer view: “Once the execution of the promise has finished, execute the function”.
By default, we use .then()
when the execution succeeds. In case it fails, we can have .catch()
, and we can even pair it with .then()
one after the other.
promise.then(function () {
// In case of success ...
}).catch(function () {
// In case of failure ...
});
With this concept in mind, we can now really understand what async and await are about.
Async-Await
While .then()
and .catch()
work perfectly fine, they are not the best thing when it comes to code readability. You have to use anonymous functions (that is, functions created on-the-fly), and so it gets hard to follow good coding practices.
Your code can get pretty messy if you have to deal with multiple nested promises. For example, you want to execute another promise inside the .then()
of a previous one, and so on.
To make things simpler, JavaScript offers us two keywords.
async
indicates that a function is asynchronous. Whenever we call that function, it will automatically run in a detached thread, parallel to our own and without blocking the main code.await
allows us to wait for an asynchronous function, or for a premise. In fact, it tells JavaScript that – instead of launching the async function separately – it should run in the main code.
If you combine both, you can create powerful combinations. In fact, you can create the same behavior as with .then()
and .catch()
, but in a more readable way.
Implementing Async-Await
The Definition
To define a function as asynchronous, use async
before the function
keyword.
async function myAsyncFunction() {
// Some code, often a promise...
}
To wait for the execution of an asynchronous, or of a promise, use the await
keyword before calling the function. You can do that only within async
functions, not in normal functions.
let result = await myAsyncFunction();
Design Considerations
The design pattern you should have in mind is that your main piece of code should reference other parts asynchronously whenever possible. This will keep your application lighter and prevent unwanted crashes.
As such, you should create some asynchronous functions to call in your main code. Then, within those functions, you can use promises or other asynchronous functions. However, since the function is already running outside of the main thread, we do not need to launch subsequent functions asynchronously: we can wait for them instead.
In this way, we launch a big asynchronous function once, and all its sub-functions can be executed synchronously inside of it.
function resolveAfterDelay(delay) {
// We create a promise that will resolve/settle after a delay
// Do that, we create a function inside the promise that calls a
// timer of *delay* milliseconds
return new Promise(resolve => {
setTimeout(() => {
resolve('Done');
}, delay);
});
}
async function myAsyncFunction() {
console.log('Starting...');
// We wait for 10k milliseconds (10s) before continuing
const result = await resolveAfterDelay(10000);
// The result is the parmeter passed by the promise
console.log(result);
// expected output: "Done"
}
// We lanch our async function, executing it outside of the main thread
myAsyncFunction();
This will yield the following output. Obviously, the Done
line will appear after 10 seconds.
Starting...
Done
Managing Errors
With .then()
we also had .catch()
, but how do we deal with error with async
and await
? Interestingly enough, we deal with them like normal errors, with try...catch
.
Our promise or async function should simply throw an error in case of a problem. Then, we can catch it and deal with it separately.
try {
const result = await myAsyncFunction();
// Continue succesful processing...
} except (error) {
// Deal with the error...
}
This is a way better approach because it means we can manage errors here like on any normal function. Much clearer to understand and simpler to maintain.
Real-World Case with Axios
In case you don’t know, axios is a library that allows you to make HTTP Calls in a better way using JavaScript. Since HTTP calls go to an external server, they take time, and they are thenable. They are a wonderful candidate to use async and await.
In case you don’t have axios in your app, you can include it with the following code in HTML.
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Then, you can easily use it with our powerful keywords. Specifically, you can use await
with it, as it is already an async function. However, you may want to wrap it in your own async function.
async function postData(data) {
try {
const result = axios({
method: 'post',
url: '/myurl',
data
});
return true;
} catch (error) {
return false;
}
}
postData({ name: 'John', last: 'Doe' });
Boom, we easily implemented a function that can post some data to our server and deal with errors.
Async-Await for Full Stack Developers
This JavaScript tutorial on async-await is part of a larger Full Stack Development Course, that is completely free. As part of the course, we are also giving you some exercises to try out what you learn.
Whatever you are following the course or not, you should try what you study a few times so that it will sink in your mind, and you will reach the nirvana of programming.
Thus, you should try the following exercise, part of the pretend bakery website we are building as part of the course. In case you want to see more of it, check it out on GitHub.com at alessandromaggio/full-stack-course.
The Assignment
This assignment is based on last week’s assignment, about HTTP Calls. Long story short, last time we created a product-of-the-day.js
file that loaded, using axios, some data inside a paragraph. You can browse the entire code for that finished exercise on GitHub here. In any case, this is the code we had in the file.
axios.get('pod.json')
.then(function (response) {
document.getElementById('pod').innerHTML = response.data.text;
}).catch(function (error) {
console.log(error)
});
Now, our assignment is simple. We need to create an async function loadPOD()
that does the same thing, but using async
and await
.
Try working on this assignment on your own before checking out the solution below. Remember that only by putting your effort and “banging your head on the wall” you will remember everything about async-await.
The Solution
So here we go with the solution. Before continuing to read, be sure to have tried hard solving the problem on your own, hopefully succeeding.
In case you want to take a peek at the entire code from this exercise, you can always check out this commit on GitHub.com, otherwise, just read below.
The first thing we know is that we need to have a loadPOD()
function and that we need to call it. Thus, we can lay the skeleton for our code.
async function loadPOD() {
}
loadPOD();
Then, since we had a .catch()
statement previously, we need to implement some try...catch
inside of it.
async function loadPOD() {
try {
} catch (error) {
console.log(error);
}
}
Now, that’s where the magic happens. We need to use await and wait for axios, then get the data from the response and put it into our element, just like before. The following two lines of code can do just that.
const response = await axios('pod.json');
document.getElementById('pod').innerHTML = response.data.text;
So finally, it all comes together with the following code snippet.
async function loadPOD() {
try {
const response = await axios('pod.json');
document.getElementById('pod').innerHTML = response.data.text;
} catch (error) {
console.log(error);
}
}
loadPOD();
I think you can clearly see how readibility is way better now.
Conclusion
With async-await, you can create complex and rich asynchronous operations while keeping your code easy to read. This is crucial because all modern applications rely on being asynchronous to manage their complexity.
With this, you have a very nice tool you can use to fine-tune your apps. If you want some further readings, you should check out how to create your custom promises.