Promises are great but the its nested syntax can get easily complex and hard to follow. With ES2017 async and await keywords for writing asynchronous code makes it more readable and easier to follow than equivalent code based on long promise chains or deeply nested callbacks.
What’s Async/Await?
The purpose of async/await functions is to simplify the behaviour of using promises synchronously and to perform some behaviour on a group of Promises. Just like Promises are similar to structured callbacks, async/await is similar to combining generators and promises.
Promises
However, the entire foundation for async/await
is promises. In fact every async function you write will return a promise, and every single thing you await will ordinarily be a promise.
Before diving deep into async/await, let’s identify what a Promise is.
What is a Promise?
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. It is a special kind of javascript object which contains another object.
A Promise is in one of these states:
- Pending – initial state, not fulfilled or rejected
- Fulfilled – meaning that the operation completed successfully
- Rejected – meaning that the operation failed
To access data in a Promise, you can use .then() as below:
function getFirstPost() {
return getPosts().then((posts) => posts[0]);
}
To capture any errors, you can add catch()
after the then()
call.
function getFirstPost() {
return getPosts().then((posts) => posts[0]).catch(error => error);
}
By using async/await we can ditch the callback function. Let’s write the first example in ES6 aync/await.
async function getFirstPost() {
const posts = await getPosts();
return posts[0];
}
Here await
will simply pause the execution of the method until the value from the promise is available.
Now let’s look how we can catch errors as in the second example above.
async function getFirstPost() {
try {
const posts = await getPosts();
return posts[0];
} catch (error) {
return error;
}
}
Much simpler isn’t it? One thing you must remember to do is not forgetting the await keyword. Otherwise you will get a promise instead of a value.
Promise chain
You can use then()
for asynchronous functions to be seamlessly called within a promise chain.
getFirstPost().then((post) => console.log(post.title, post.content));
Awaiting multiple values
If you are calling multiple async functions, for example the following:
const post = await getFirstPost();
const user = await getFirstUser();
Here, you can only await on one at a time. So you will get the results sequentially, not at the same time.
To make it work we have to do something like this:
const [foo, bar] = await Promise.all([getFirstPost(), getFirstUser()]);
Is await
can be only used with promises? Not all, you can you await with any Thenable
.
So what does Promise.all
mean? Let’s clarify that first.
Promise.all
However, the entire foundation for async/await
is promises. In fact every async function you write will return a promise, and every single thing you await will ordinarily be a promise.
So can you convert a callback function to async? let’s discuss this bit further.
Converting any function into an Async
Any function can be made asynchronous, including function expressions, arrow functions, and methods.
if you understand how promises work, you can get around this by treating the result of an async function as a promise, which itself accepts callbacks.
function getFirstPost(callback) {
return getPosts().then(function(post) {
return callback(undefined, post.name);
}).catch(function(err) {
return callback(err);
});
}
It’s important to handle errors properly when it comes to promises. You need to catch errors properly handle them or the error can be lost on the way.
Async iterators with for-await-of
There is an asynchronous iteration scheme, built on top of a new for-await-of
loop and async generator functions.
Symbol.asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator');
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function* generator() {
await delay(1000);
yield 1;
await delay(1000);
yield 2;
await delay(1000);
yield 3;
}
for await (const value of generator()) {
console.log(value);
}
Understanding promises is the key to asynchronous JavaScript programming. This article hopefully will help you in understanding how async/await works.