Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 21, 2021 05:42 am GMT

Understanding Async Await In Javascript

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 promiseasync 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 functionfunction 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, its totally the opposite, callbacks cant 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  undefinedinside 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 namesafter printing namesHello 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.

// promiseasync1().then(x => asyncTwo(x)).then(y => asyncThree(y))//other statementconsole.log("hello")//async awaitx = 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


Original Link: https://dev.to/saroj990/async-await-a-cure-for-promise-hell-52i5

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To