Understanding Async Await In Javascript
saroj sasmal
Posted on May 21, 2021
In this article, we are going to explore async/await
which is the go-to tool for every javascript developer for async programming. If you're fairly new to javascript, don't worry, this article will help you understand async/await
from the ground up.
Introduction
async/await
is a pattern in javascript which makes your code execute in a synchronous fashion but without compromising the async behaviour of javascript.
Defining an Async Function
To define an async function, all you need to do just prepend an async keyword before the function definition.
// async function always returns a promise
async function greet() {
return "hello";
}
Easy-peasy!😎. Using the async keyword before a function name
makes that function return a promise.
resolves when the function returns.
finally rejects when an error is thrown.
It means you don't need to declare the return Promise.new() each time you want to create a promise.
To prove that an async function returns a promise, we can quickly attach a then block to print its value.
async function greet() {
return "Hello from an async function"
}
greet().then(message => console.log(message));
//Hello from an async function
Using Await and Executing Async Functions
Isn't cool that we can do then()
and catch()
on an async
function ? But that's not the real power of an async function, an async
function's real potential lies in await
statements.
await
makes the function to be executed in a synchronous way while holding the control in that line until the awaiting method has finished its execution.
async function greet() {
return "Hello from an async function"
}
async function execute() {
const message = await greet();
console.log(message)
}
Here are a few rules of thumb that we need to remember.
👉 await can only be used inside an async function
A function must be declared async
if we use the await inside it but not the other way around.
Let me put it in this way. If an await
statement is used inside a method, that method must be an async
method, else the compiler will yell at us.
async function greet() {
return "Hello from an async function";
}
function execute() {//this function must be async
const message = await greet();
console.log(message)
}
/*
SyntaxError: await is only valid in async function
*/
~~~{% endraw %}
But declaring a function {% raw %}`async`{% endraw %} doesn't necessarily mean we would always use an {% raw %}`await` inside it. Here `greet()` is an `async` method but we don't have any {% raw %}`await`{% endraw %} statements inside it.
👉 *await makes sense when the function it is called on, returns a promise or is an async function*{% raw %}
~~~javascript
//not an async function
function greet() {
return "Hello from an async function";
}
async function execute() {
const message = await greet();
console.log(message); //Hello from an async function
}
~~~{% endraw %}
Although the code works exactly the same as the previous one, doing an {% raw %}`await`{% endraw %} on a {% raw %}`synchronous`{% endraw %} function does not make any sense. I would like to know what are your thoughts on this ?🤔🤔.
One important aspect of using await is the fact that it blocks the execution of the next lines of code until the await block is executed.
{% raw %}
```javascript
const asyncGreet = () => new Promise(resolve => setTimeout(resolve, 2000));
(async function execute() {
console.log("before executing");
await asyncGreet(); //blocks execution here
// 👇 executed once await is finished
console.log("I will be executed after 2000ms");
})();
```
{% endraw %}
Now you must be wondering if *await* makes the code synchronous, why should we use it? NodeJs or browser Javascript are single-threaded environments and execute one task at a time and widely used because of their asynchronous behaviour, which we're losing. So what is the point?
Yes, you're right that but if you observe in most of the cases, we need to perform a task in relation to others.
{% raw %}
```javascript
async function subscribeToNewsLetter() {
const user = await findUser(id);
//👇methods need user email to execute
await subscribe(user.email)
await sendNotification(user.email)
}
```
{% endraw %}
That's correct. but what about code that is not related to each other? Well, there is an alternative for that as well i.e. (`Promise.all`).
```javascript
const asyncGreet = (name) => new Promise((resolve) => setTimeout(resolve(`Hello ${name}`), 2000));
const names = ['john', 'jane', 'david'];
(async function() {
const greetingPromises = names.map(name => asyncGreet(name));
console.log(await Promise.all(greetingPromises));
})();
```
I know the above code is a contrived example, what is important here is that we are using the power of `Promise.all` to execute all the promises
#### Handling Errors in `Async/Await`.
Dealing with errors is pretty easy with *async/await*, we can use our old friend the *try/catch* block for achieving this.
```javascript
async function subscribeToNewsLetter() {
try {
const user = await findUser(id);
await subscribe(user.email)
await sendNotification(user.email)
} catch(err) {
//handle error
}
}
```
There is also another version where we can attach a *catch* handler directly to the *await* block. I don't use it personally but you can give it a try if you want👍.
{% raw %}
```javascript
await asyncGreet().catch(err => console.log(err);
```
{% endraw %}
#### 2x Readability, Easy Debugging
The following code uses a *Promise* to find the user by *id*, assigns the profile information, and then finds the user's subscription.
```javascript
function getUser(id, profile) {
return new Promise((resolve, reject) => {
User
.find(id)
.then((user) => {
if(_.isEmpty(user)) return {};
user.profile = profile;
return user;
})
.then((user) => Subscription.find(user.id))
.then(subscription => {
if(_.isEmpty(subscription)) {
user.subscription = null;
} else {
user.subscription = subscription;
}
return resolve(user)
})
.catch(err => reject(err))
})
}
```
The above code works perfectly fine, but we could definitely make it more readable, concise, and easier to debug with `async`/`await`. Let's give it a go.
{% raw %}
```javascript
async function getUser(id, profile) {
try {
const user = await User.find(id);
if(_.isEmpty(user)) return {};
user.profile = profile;
const subscription = await Subscription.find(user.id);
user.subscription = subscription
return user;
} catch(err) {
console.log(err);
}
}
```
{% endraw %}
#### Callbacks and {% raw %}`Async/Await`{% endraw %} are Enemies
As we already saw in our previous example, promises play really well with {% raw %}`async`{% endraw %}/`await`. Any function that returns a promise can be used with `await` statement.
But when it comes to callbacks, it’s totally the opposite, callbacks can’t be directly used with {% raw %}`async`{% endraw %}/`await`, they must be converted to a promise.
let's consider the following function which asynchronously tests if a value is even or not(raise an error).
```javascript
function asyncEven(id, cb){
setTimeout(() => {
const even = id%2 === 0;
if (even) return cb(null, "even");
else return cb("not even");
}, 2000);
}
```
We know await is not permissible on callback but still, let's give it a try.
{% raw %}
```javascript
(async function() {
//🐶👹 Wrong way
const even = await asyncEven(2);
console.log("isEven ", even); //undefined
})();
```
{% endraw %}
You must be thinking, that we didn't attach a callback that's the reason it printed {% raw %}`undefined`{% endraw %}.
Let's attach a callback, which is super weird but let's have patience.
{% raw %}
```javascript
(async function() {
//this is also wrong 🐶👹
const even = await asyncEven(2, (err, data) => { console.log("inside await on callback", err, data)});
console.log("isEven ", even);
})();
/*
output:
even undefined
inside await on callback even null
*/
```
{% endraw %}
It seems like the callback was called and we also got values from the asyncEven function. That's correct but still, it is a wrong approach.
`await` has no impact on callback. it similar to doing an await on a synchronous function.
Then why did it return *undefined*? That's a good question. This the default nature of asynchronous programming. The *setTimeout* function is a callback that returns a value via the callback after 2000ms, meanwhile, the control start executing the next line of code, and it reaches the end of the function, that is why we get an *undefined*.
So what is the solution? Pretty simple. Turn the {% raw %}`asyncEven`{% endraw %} function to a promise and use {% raw %}`await`{% endraw %} like a champ.
{% raw %}
```javascript
function asyncEven(id,) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const even = id%2 === 0;
if (even) return resolve("even");
else return reject("not even");
}, 2000);
})
}
(async function() {
// waits for the execution
const even = await asyncEven(2);
console.log("iseven ", even);
})();
```
{% endraw %}
#### ForEach Does Not Play Well with {% raw %}`Async/Await`{% endraw %}
ForEach loop may have side effects if we use it with {% raw %}`async/await`{% endraw %}. Consider the following example, the {% raw %}`console.log`{% endraw %} statement here doesn't wait for the `await greet(name)`.
```javascript
async function greet(name) {
return Promise.resolve(`Hello ${name}, how are you ?`);
}
(function() {
console.log("before printing names");
const names = ['john', 'jane', 'joe'];
names.forEach(async (name) => {
//does not wait here
console.log(await greet(name));
});
console.log("after printing names");
})();
/*
before printing names
after printing names
Hello john, how are you ?
Hello jane, how are you ?
Hello joe, how are you ?
*/
```
#### More Than Just a Syntactic Sugar
So far we only know that `async/await` makes our code more readable, debug friendly and some people say it's a syntactic sugar on javascript promises. In reality, it's more than just a syntactic sugar.
```javascript
// promise
async1()
.then(x => asyncTwo(x))
.then(y => asyncThree(y))
//other statement
console.log("hello")
//async await
x = await async1();
y = await asyncTwo(x);
await asyncThree(y);
```
`await` suspends the execution of current function, while promise continues executing the current function adding the value to the `then()`. There is a significant difference between these two way of executing programs.
Let me explain, consider the promise version, if *asyncTwo()* or *asyncThree()* throws an async error while performing a task, will it include `async1()`in the stack trace ?
Here promise does not suspend the execution of current function, by the time `asyncTwo` resolves or rejects, the context is out of the promise statement. So ideally, it not able to include `asyncOne` in the stack trace . But thanks to V8 engine, it does some magic here, by keeping reference to `asyncOne()` ahead of the time in order to include `asyncOne()` in the context. But this does not come for free. Capturing the stack trace takes time (i.e. degrades performance); storing these stack traces requires memory.
This is where `async/await` beats promises in terms of performance, as the execution of current function is halted until the awaiting function is finished, so we already a have a reference to the function.
------
*Thanks for reading this article, I hope this post was helpful in understanding the async/await feature of javascript. If you like my article, please show your love by liking this post, this would mean so much to me. Meanwhile you can check out my [article] (https://dev.to/saroj990/mastering-javascript-promises-4kfh) on javascript promises.*
References:
[https://mathiasbynens.be/notes/async-stack-traces](https://mathiasbynens.be/notes/async-stack-traces)
Posted on May 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024