Future JavaScript #2 — Async Functions
A series exploring next generation JavaScript syntax
Hey everyone, welcome to the second part of the series.
This week, we will go over and explain what Async Functions are and how we can use them to write better code. They are an ES7 proposal but we can use them today with great tools like Babel or Traceur. If you don’t know what these are, you better check them out and thank me later.
We all know how confusing callbacks can be, especially when it’s not clear what will be the order of execution and at which moment what value is available.
What Async Functions accomplish is to make asynchronous operations easier. They do that by making them look synchronous by handling them under the hood and giving us a predictable execution order.
We will cover the following:
- Syntax
- How they work
- Examples
- TL;DR
Syntax
There are two parts in specifying an Async Functions.
The first is the function itself and how it should be declared. We should use the async keyword before the function declaration. This gives us the ability to later use await in the function. For ES5, we put async before function. For ES6, we put it before the parenthesis or before the name for methods.
// ES5
async function get() {}
async function() {}// ES6
const get = async () => {}
async () => {}const obj = {
async get() {}
}
The second part is using the await keyword in the function, which makes the execution synchronous. Since the rest of the execution will wait, we don’t have to worry that the function will return before the result is assigned.
const post = await http.get("/posts/1");
How they work
Well, let’s be honest. Using await doesn’t make execution synchronous, it just makes it look like it is. It just handles the Promise under the hood for us and yields the execution until it is resolved or rejected.
Even with that revelation, it’s still a big win. Now we are able to write code that looks synchronous, but is actually asynchronous. This makes one of the complex functionalities of the language simpler for everyone.
Let’s see what using async and await together looks like.
async function get(url) {
const result = await http.get(url);
if (result.statusCode !== 200) alert("Something went wrong");
return result;
}get("posts").then(result => { console.log(result); });
There are couple of things that must be clarified in order to utilise Async Functions to their full power.
Async
Using the keyword async accomplishes two things.
- Allows us to use await in the function. It’s important to remember that we can use it only directly in the function on which we set async. If we have an inner function and we want to use await in it, we should set async on it as well.
- Turns everything returned from the function into a Promise. Later when the result from await arrives, the Promise is resolved with that value.
Await
Using the await might seem strange at first but keeping the following in mind should help you reason about what is going on.
- In order to use await, the direct function that contains it should be async.
- Internally await statements work with Promises. They just hide their implementation, making it easier to work with them.
- The rest of the function continues its execution only when the Promise is resolved or rejected.
- If we don’t return a Promise from the function used with await, the execution will proceed normally, as though we haven’t used await at all.
Benefits
Synchronous programming is more simple. We can clearly see what should happen in order for certain code to execute. Also the returned results and the order in which things happen are clear.
Having the ability to think synchronously and still use the asynchronous Promises, gives us a great opportunity to make better programs and to avoid errors that are sometimes hard to spot.
Examples
Let’s take a look at couple of real-world examples to see how we can use async functions.
Fetch Posts
Let’s suppose we have a super simple http module that only fetches results from a public API in this case http://jsonplaceholder.typicode.com.
The only way it will work with Async Functions is if it returns a Promise. Promises are nothing new, so I think there is nothing to explain here.
We will use the superagent library for the request, since it’s simple and clear.
import request from “superagent”;const BASE_URL = “http://jsonplaceholder.typicode.com";const http = {
get(route) {
return new Promise((resolve, reject) => {
request
.get(`${BASE_URL}/${route}`)
.set(“Accept”, “application/json”)
.end((err, res) => {
if (err) {
reject(err);
return;
} resolve(res.body);
});
});
}
};
Now when we have a way to make the request we can use it with an Async Function.
Let’s create an all purpose fetch function that takes a route parameter and handles any errors that might arise. What is important to notice is that everything is synchronous. If the Promise is resolved, we have a result with which we can do whatever we like and finally return it, so it’s passed along.
const fetch = async route => {
try {
const result = await http.get(`${route}`);
// Do something with result
return result;
} catch (err) {
if (err && err.response) {
// Handle error
}
}
};fetch(“posts”).then(posts => {
if (posts) show(posts);
});
Remember that an Async Function turns whatever we return into a Promise. If there is an error and we don’t have any return statement, the function will return undefined. When the returned value is undefined, the result passed to then will be undefined, so make sure that you check for that case.
Fetch specific posts by ID
As we said, every time we use an await the outer function should be async.
Let’s create a function that fetches resources by the passed IDs. This is not very efficient because it will actually make several requests, instead of only one, but for the sake of this tutorial, we will do it.
It will take the route as first parameters. We can then pass variable number of IDs and will be using the rest parameters syntax to handle them.
const fetchById = (route, ...ids) => {
const posts = ids.map(async id => {
try {
const post = await http.get(`${route}/${id}`);
// Do something with post
return post;
} catch (err) {
if (err && err.response) {
// Handle error
}
}
}); return Promise.all(posts);
};fetchById("posts", 2, 34, 57).then((posts = []) => {
posts = posts.filter(n => n);
if (posts) show(posts);
});
Since we are not using await in the outer function anymore we don’t need the async keyword. Now we should set it to the anonymous function we pass when we iterate over the IDs.
It’s important to remember that async should be set exactly to the function that contains the await statement or in this case to the anonymous function, and not to the outer fetchById function.
What is returned by the iteration is actually an array of promises. Then we can pass them to Promise.all(), which takes resolves only after each one has been resolved/rejected.
After that we just check and filter for any undefined posts and show them. We do this, because there is a possibility a resource with the passed ID to not exist.
TL;DR
- async functions turn every returned value into Promise
- to use await the direct containing function should be async
- await should return a Promise, if not it works normally
- await halts execution until the Promise is resolved/rejected
const fetch = async url => {
const result = await http.get(url);
alert(result.length);
return result;
}fetch("posts/1").then(post => { show(post); });
Conclusion
Although still a proposal and not part of the JavaScript language, we can use Async Functions today by using a transpiler. The benefits of using one don’t stop here, so if you are not, you should really try.
Async Functions give us an alternative to callbacks and makes Promises easier to use.
It’s worth taking the time to understand and start using them, not only because they are new, but because they are actually useful.
Table of contents
Previous Article:
Next Article:
const / let / var