This content originally appeared on DEV Community and was authored by saroj sasmal
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
*/
But declaring a function async
doesn't necessarily mean we would always use an await
inside it. Here greet()
is an async
method but we don't have any await
statements inside it.
? await makes sense when the function it is called on, returns a promise or is an async function
//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
}
Although the code works exactly the same as the previous one, doing an await
on a synchronous
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.
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");
})();
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.
async function subscribeToNewsLetter() {
const user = await findUser(id);
//?methods need user email to execute
await subscribe(user.email)
await sendNotification(user.email)
}
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
).
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.
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?.
await asyncGreet().catch(err => console.log(err);
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.
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.
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);
}
}
Callbacks and Async/Await
are Enemies
As we already saw in our previous example, promises play really well with async
/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 async
/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).
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.
(async function() {
//?? Wrong way
const even = await asyncEven(2);
console.log("isEven ", even); //undefined
})();
You must be thinking, that we didn't attach a callback that's the reason it printed undefined
.
Let's attach a callback, which is super weird but let's have patience.
(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
*/
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 asyncEven
function to a promise and use await
like a champ.
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);
})();
ForEach Does Not Play Well with Async/Await
ForEach loop may have side effects if we use it with async/await
. Consider the following example, the console.log
statement here doesn't wait for the await greet(name)
.
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.
// 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 on javascript promises.
References:
https://mathiasbynens.be/notes/async-stack-traces
This content originally appeared on DEV Community and was authored by saroj sasmal
saroj sasmal | Sciencx (2021-05-21T05:42:51+00:00) Understanding Async Await In Javascript. Retrieved from https://www.scien.cx/2021/05/21/understanding-async-await-in-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.