0

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?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • `async` implicitly returns a `Promise`, so the difference is mainly the function signatures. – zero298 Nov 18 '20 at 21:00
  • @zero298 well that and also *when* either of these actually finishes. First one completes after `getOneOrg` finishes, the second one *before* it finishes. – VLAZ Nov 18 '20 at 21:01

3 Answers3

1

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.

Lemon
  • 148
  • 10
  • res retuns a http response. to update the categories he has to write : res.data.ans – Hamza Khattabi Nov 18 '20 at 21:06
  • I think this is kind of high level for me. I thought async/await and .then() kinda make it into a background process? Like is there an example of not using the .then() method but needing to use async/await? – smammadov94 Nov 18 '20 at 21:27
  • 1
    @smammadov94 async/await are pretty much syntactic sugar over the promise API. There isn't really any `await` usage that cannot be expressed as the promise API. The majority of promise API usages are also expressible via async/await. The most notable thing you *cannot* do via `await`-ing is `Promise.all()`, `Promise.allSettled()`, `Promise.any()`, and `Promise.race()`. – VLAZ Nov 18 '20 at 21:35
  • Yes as @VLAZ said is a syntax sugar. There are some differences behind the scenes but in fact the idea of use async/await is to replace .then() If you don't use async/await you can easily fall into a something called "Promise hell" – Lemon Nov 19 '20 at 14:03
  • 1
    @Lemon you *shouldn't* fall into a promise hell. The whole design goal behind promises is to avoid the [callback hell pattern](http://callbackhell.com/) by making them chainable and auto-merging/flattening the promises. [They aren't the same](https://stackoverflow.com/questions/22539815/arent-promises-just-callbacks) - if you start nesting the promises, then *you* are doing something wrong, it's not the fault of promises. Still, that doesn't mean that the `.then` syntax is always the same. Sometimes, the code is easier to read using `await` but not because excessive nesting. – VLAZ Nov 19 '20 at 15:00
0

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.

Phix
  • 9,364
  • 4
  • 35
  • 62
  • "*The first example should be along the lines of: [...] `await getOneOrg(value.label).catch(err => console.log(err))`*" ??? Wasn't your point that you shouldn't mix promises API with async/await? – VLAZ Nov 18 '20 at 21:10
  • I think I kind of understand but in terms of just fetching. Basically what I am seeing here is that .then() is the same as if you create a function like the one above. So using .then() will still wait for the function to complete and not using it just kinda passes by it and it happens sometime in the background? – smammadov94 Nov 18 '20 at 21:26
  • This wasn't a comprehensive breakdown of async/await. You can wrap in a try/catch as well. – Phix Nov 18 '20 at 21:26
  • @smammadov94 it's the same functionality, with less clutter. You can either use `then` in non-async functions or use `await` in async functions. – Phix Nov 18 '20 at 21:27
  • Still a little confused, I don't know why but I cannot wrap my head around it. I kind of want to understand an example where I will use async/await over .then(). – smammadov94 Nov 18 '20 at 21:29
  • So basically it literally is the same thing and there is never a case where you have to use one over the other? – smammadov94 Nov 18 '20 at 21:30
  • @smammadov94 your two examples are ***NOT*** the same as each other! There is a crucial difference if you were to call `await handleChangeSelect()` - with your first code you'd wait for the entire operation, including the `getOneOrg` call to finish. In the second code, you'll *not* wait for it. – VLAZ Nov 18 '20 at 21:37
  • Okay this is what I wanted to know! So, if we use 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) } then the thread waits on that backend all before executing the rest? – smammadov94 Nov 18 '20 at 21:45
  • @smammadov94 the answer is "no" because the thread *doesn't* wait. The function execution pauses and once the promise is settled, the execution resumes. However, if you have `await handleChangeSelect()` it will work *as if* the whole execution stopped. Because you'd get the sequencing of call handleChangeSelect -> handleChangeSelect finishes -> code *after* handleChangeSelect runs. However, if you use the second code from your example, you'd get a different sequence: call handleChangeSelect -> code *after* handleChangeSelect runs -> handleChangeSelect finishes – VLAZ Nov 18 '20 at 21:50
0

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();
VLAZ
  • 26,331
  • 9
  • 49
  • 67