smammadov94
smammadov94

Reputation: 41

React/Javascript question about async/await

I just get confused sometimes with the use of async/await. I tried to learn it a lot but I always end up questioning myself. So here is an example. I have a imported function which calls the backend on my react-app to ultimately talk to mongodb. My question is, what is the difference between using:

async function handleChangeSelect(value,action){
  await getOneOrg(value.label).then(res=>{
        const updatedCategory = {...modal, [action.name]:value, categories:[{value:res.ans,label:res.ans}]}
        setModal(updatedCategory)
    }).catch(err=>console.log(err))
}

VS.

function handleChangeSelect(value,action){
   getOneOrg(value.label).then(res=>{
        const updatedCategory = {...modal, [action.name]:value, categories:[{value:res.ans,label:res.ans}]}
        setModal(updatedCategory)
    }).catch(err=>console.log(err))
}

They both seem to work and do the same thing. Like when do I need to use async await (I see people put it on the parent function of a .then. I know fetch/.then is already async so you do not need to but then when do you even need to?). What is the point of putting it in the parent function. I just find myself extremely confused on when to use this option and for what purpose. I need examples, I have a hard time just grasping a concept. Also, what exactly is happening in the hardware/software when you write this?

Upvotes: 0

Views: 296

Answers (3)

VLAZ
VLAZ

Reputation: 29022

There is a crucial difference between your two examples. It lies how they would handle being called with await. I'll represent the two in simplified terms.

First code block:

const someAsyncOperation = ms => new Promise(res => setTimeout(res, ms, "hello"))

async function foo(){
  console.log("start async");
  await someAsyncOperation(1500)
    .then(res => console.log("do something with result:", res + "world"))
    .catch(() => console.error("no error will happen"));
  console.log("finish async");
}

async function main() {
  console.log("before foo()");
  await foo();
  console.log("after foo()");
}

main();

Result:

before foo()
start async
do something with result: helloworld
finish async
after foo()

vs second code block:

const someAsyncOperation = ms => new Promise(res => setTimeout(res, ms, "hello"))

async function foo(){
  console.log("start async");
  someAsyncOperation(1500)
    .then(res => console.log("do something with result:", res + "world"))
    .catch(() => console.error("no error will happen"));
  console.log("finish async");
}

async function main() {
  console.log("before foo()");
  await foo();
  console.log("after foo()");
}

main();

Result:

before foo()
start async
finish async
after foo()
do something with result: helloworld

As you can see, the sequence of actions is different in both cases.

  1. In the first case, an await will make the entirety of foo() finish before continuing.
  2. In the second, there is no await, so someAsyncOperation is a fire and forget. Your code will finish executing before it does, so you'll never be notified for success or failures.

Also, I have to note that this is only if you call the functions with await. If you don't, then the code will never wait for it to finish in either case.

const someAsyncOperation = ms => new Promise(res => setTimeout(res, ms, "hello"))

async function foo(){
  console.log("start async");
  await someAsyncOperation(1500)
    .then(res => console.log("do something with result:", res + "world"))
    .catch(() => console.error("no error will happen"));
  console.log("finish async");
}

async function main() {
  console.log("before foo()");
  foo(); //no await
  console.log("after foo()");
}

main();

const someAsyncOperation = ms => new Promise(res => setTimeout(res, ms, "hello"))

async function foo(){
  console.log("start async");
  someAsyncOperation(1500)
    .then(res => console.log("do something with result:", res + "world"))
    .catch(() => console.error("no error will happen"));
  console.log("finish async");
}

async function main() {
  console.log("before foo()");
  foo(); //no await
  console.log("after foo()");
}

main();

The two operations are essentially the same. There is a difference where "finish async" shows up but only because I added it for clarity of how the function is handled. In your code, you don't have anything else after the promise is fired off, so there is not going to be a difference. In both cases foo() itself is a fire and forget, since it's not awaited. Therefore, it doesn't matter if you await the operation inside or not.

At any rate, there isn't exactly a generically "better" way to use promises out of these.

Sometimes you may want a fire and forget functionality so that you don't really wait. As a simple example:

showSpinner();
getData()
    .then(data => {
        hideSpinner();
        showData(data);
    })
    .catch(error => {
        hideSpinner();
    }

/* do more stuff */

Presumably, it's some sort of non-crucial data - we can show it or not but we want to remove the spinner.

Other times, you may actually want to wait and verify an operation succeeds or not before continuing. For example something like:

try {
    const user = await registerUser(userInfo);
    await processPayment(user);
} catch (error) {
    cancelTransaction();
    throw new RegistrationAndPaymentError(error);
}

/* do more stuff */

If the registration fails, we have to pump the breaks and avoid continuing with the process.

Which one you choose depends on how you want to handle a given operation. Some you don't really care when they complete and how, others may prevent further operations.

Also worth clarifying that every async/await usage can be changed to the promise API by chaining .then() and .catch(). However, sometimes chaining a lot of promise operations is not as readable as using awaits. Most of the promise API operations can also be expressed using async/await. So, which one you choose is often based on which one you prefer. It's generally advisible not to mix the two types of syntax - nothing would go wrong if you do but it's clearer if you stick to one or the other.

With all that said, it's also advisable to make your functions with asynchronous operations awaitable. The reason is that perhaps right now you may not want to wait for them to finish but in the future you might.

With your first bit of code await handleChangeSelect() will already force the execution to pause until the function is complete so it's basically OK as it is. Granted, it would be better if it didn't mix await with .then() and .catch() but it's still not wrong.

The way to make it possible to react to a function finishing without adding an await in it (essentially, by using only the promise API), you need to return the promise that the inner function produces. So you can change it to:

function handleChangeSelect(value,action){
   return getOneOrg(value.label).then(res=>{
        const updatedCategory = {...modal, [action.name]:value, categories:[{value:res.ans,label:res.ans}]}
        setModal(updatedCategory)
    }).catch(err=>console.log(err))
}

This will make it possible to react to the function completion:

const someAsyncOperation = ms => new Promise(res => setTimeout(res, ms, "hello"))

async function foo(){
  console.log("start async");
  return someAsyncOperation(1500)
    .then(res => console.log("do something with result:", res + "world"))
    .catch(() => console.error("no error will happen"))
    .then(() => console.log("finish async")); //we don't want any code after the async call
                                              //so, further code will be chained as .then()
}

async function main() {
  console.log("before foo()");
  await foo();
  console.log("after foo()");
}

main();

Upvotes: 0

Lemon
Lemon

Reputation: 158

The thing is that in the first example you are not really using async/await. The code should be:

async function handleChangeSelect(value,action){
  try {
    const res = await getOneOrg(value.label)
    const updatedCategory = {...modal, [action.name]:value, categories:[{value:res.ans,label:res.ans}]}
    setModal(updatedCategory)
  }
catch(err) { console.log(err)}
}

If you have many promises concatenated the use of async-await result in a cleaner and more understandable code.

I don't want to enter in details on the use and the behind the scenes because there are a lot of resources online.

Upvotes: 1

Phix
Phix

Reputation: 9890

The first is "incorrect" usage of async/await. From the docs:

An async function is a function declared with the async keyword. Async functions are instances of the AsyncFunction constructor, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

The first example should be along the lines of:

async function handleChangeSelect(value, action) {

  const res = await getOneOrg(value.label).catch(err => console.log(err))

  const updatedCategory = {
    ...modal,
    [action.name]: value,
    categories: [{
      value: res.ans,
      label: res.ans
    }]
  }
  setModal(updatedCategory)
}

This waits for the getOneOrg function to resolve before continuing onto updating the updatedCategory object.

Short answer - it removes the need for chaining .then() everywhere.

Upvotes: 0

Related Questions