77

Is there any harm in using async/await and .then().catch() together such as:

async apiCall(params) {
    var results = await this.anotherCall()
      .then(results => {
        //do any results transformations
        return results;
      })
      .catch(error => {
        //handle any errors here
      });
    return results;
  }
Shubham Dixit
  • 9,242
  • 4
  • 27
  • 46
PixelPaul
  • 1,022
  • 1
  • 10
  • 18
  • 3
    no, there is no harm. You are only passing the final value of the promise chain to the `result` variable using `await`. – yqlim Mar 06 '19 at 10:00

9 Answers9

68

I always use async/await and .catch() instead of using async/await and try/catch to make code compactly.

async function asyncTask() {
  throw new Error('network')
}
async function main() {
  const result = await asyncTask().catch(error => console.error(error));
  console.log('result:', result)
}

main();

If you want to get a fallback value when an error happened, you can ignore the error and return a value inside the .catch() method

async function asyncTask() {
  throw new Error('network')
}
async function main() {
  const result = await asyncTask().catch(_ => 'fallback value');
  console.log('result:', result)
}

main();
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Your way hadn't occurred to me, but I really like it! I usually mix them the opposite way, having a try / catch statement with await and using .then to perform an additional transformation or whatever on the resolved promise. – quantumferret Nov 16 '20 at 11:30
  • 1
    Let's say you have more code after the `result =` line. Let's say that there was an error. Is `result` then undefined? – Jeff Padgett Jan 07 '21 at 05:16
  • @JeffPadgett Yes, you can return a fallback value from the `.catch()` method – Lin Du Mar 04 '21 at 01:51
  • Do you mind updating your answer with a fallback from the `.catch()` method? – Jeff Padgett Mar 04 '21 at 18:34
  • 1
    @JeffPadgett Updated answer. – Lin Du Mar 05 '21 at 01:49
  • Really nice and compact, the fallback value is a very good way to avoid Typescript error – ndh103 Sep 20 '21 at 14:10
  • 1
    your code doesn't include any `then` clause at the same time of using `await`, which is exactly what the question is about! – S.Serpooshan Feb 06 '23 at 07:03
  • This answer doesn't emphasize it enough, but removing `.then` is the critical point here to avoid confusing callbacks when used along with `await`. `catch` can be OK to avoid `try`/`catch` nesting for a specific call, but that seems like an afterthought. – ggorlen Mar 19 '23 at 19:33
43

Don't want to raise the dead, but want to point out that using await along with a then chain means that the result of:

const x = await someAsyncFn().then(() => doSomeLogging());

The value of x is assigned the return value of .then (i.e. undefined, if doSomeLogging is void) which wasn't super intuitive to me.

Bricky
  • 2,572
  • 14
  • 30
  • 1
    What is the point of adding `await` if you are going to wait for the value in `.then` and how does the code work, what will be the value of `x`? – Mohammed Shahed Nov 29 '22 at 07:27
  • 2
    @MohammedShahed there isn't a point, it's just something that people might do if they're not deeply familiar with both paradigms. The point is that the two different approaches do basically the same thing, but they do not always play well together. – Bricky Nov 29 '22 at 23:25
27

An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.

As you can see from below example that you can use two ways to handle await result and errors,The keyword await makes JavaScript wait until that promise settles and returns its result (One you get from resolved promise).So as such there is no harm (I don't fully understand what you refer as harm here).

function returnpromise(val) {
  return new Promise((resolve, reject) => {
    if (val > 5) {
      resolve("resolved"); // fulfilled
    } else {
      reject("rejected"); // rejected
    }
  });
}

//This is how you handle errors in await
async function apicall() {
  try {
    console.log(await returnpromise(5))
  } catch (error) {
    console.log(error)
  }
}

async function apicall2() {
  let data = await returnpromise(2).catch((error) => {
    console.log(error)
  })
}

apicall2();
apicall();

For further reference have a look at-MDN DOCS

Shubham Dixit
  • 9,242
  • 4
  • 27
  • 46
10

I don't think mixed use async/await + then is a good idea. Especially when you focus on the async func's res, mixed use will bring some risk to distort your res silently.

example:

const res = await fetchData().then(()=>{return "something else"}).catch((err)=>{});

console.log(res); // not expected res data but something else

So, mixed use is dangerous , by the way, it'll do harm to read codes.

PeterYuan
  • 101
  • 1
  • 4
9

If you use Async/await you don't need to chain .then() just store the result returned by you resolve() in a variable (response in the example) but if you want to handle the errors you have to try/catch your code :

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

in your promise use:

throw new Error("oops!");

Or

Promise.reject(new Error("Whoops!"));

  • i use that all the time and it's better than using .then + another anonymous async function that makes the whole thing illegible – scavenger Sep 13 '20 at 19:55
  • You don't _have_ to `try`/`catch` here. You can use `.then()` and `.catch()` along with `await`. The question is whether it's good to do so or not. – ggorlen Mar 19 '23 at 19:31
5

I think there's some confusion throughout this thread as to what "harm" entails. If you define harm as (merely) "does OP's exact code operate properly?", then it's not harmful.

However, if you define harm as "this is a difficult-to-read, error-prone antipattern that tends to cause bugs and is never truly necessary to resort to", then it's indeed harmful.

There are innumerable questions on Stack Overflow where OP has mixed .then and await and wound up with a bug. I've selected a few for inclusion at the bottom of this post.

As a simple rule of thumb, never combine await and then in a function. At best, it's harder to read than using one or the other, and at worst, it's hiding a bug, usually related to error handling.

Generally, prefer async/await over .then. If you're having a hard time determining when to use which, feel free to go a step further and avoid .then and .catch completely.

That said, I like to occasionally use .then which can be a bit less verbose when error-handling is involved and there's no need to access state on an object that can't be readily passed through the chain:

fetch("https://www.example.com")
  .then(response => {
     if (!response.ok) {
       throw Error(response.statusText);
     }

     return response.json();
   )
  .then(data => console.log(data))
  .catch(err => console.error(err));

Seems cleaner to me than:

(async () => {
  try {
    const response = await fetch("https://www.example.com");
  
    if (!response.ok) {
      throw Error(response.statusText);
    }

    console.log(response.json());
  }
  catch (err) {
    console.error(err);
  }
})();

With top-level await, the bottom code becomes more appealing, although in practice, you're usually writing a function.

An exception I agree with is given by this answer, which is to occasionally use .catch on an await chain to avoid a somewhat ugly try/catch.

Here's an example of when this might be useful:

const fs = require("node:fs/promises");

const exists = async pathName =>
  !!(await fs.stat(pathName).catch(() => false));

May be preferable to the async/await/try/catch version:

const exists = async pathName => {
  try {
    await fs.stat(pathName);
    return true;
  }
  catch (err) {
    return false;
  }
};

...or maybe not depending on if you feel the catch version is too clever.

Note that there is no .then and await mixing here, just .catch rather than try/except. The general heuristic at play here is "flatter is better" (.catch being flatter than try/catch and await being flatter than .then).

(Yes, the example is somewhat contrived, since there's a pretty clean way to use .then/.catch alone for this particular task, but the pattern can appear in other contexts from time to time)

If there's any doubt, stick to the "never mix" rule.


As promised, here's a small selection of examples I've seen of confusion caused by mixing await and then (plus a few other promise antipatterns):

And related threads:

ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • 1
    "*An exception I agree with*" - see also my answer on [try-catch syntax in async/await](https://stackoverflow.com/q/44663864/1048572). I do actually also like using `.then()` with two handlers instead of `.catch()`- it would also be appropriate in your first snippet, for example – Bergi Mar 19 '23 at 22:34
  • Thanks, that makes sense, but I think the vast majority of `await`/`then` mixing is problematic and coming from a place of confusion. It takes a lot of experience to know the few edge cases where it can be judiciously employed, so I think it's better to recommend that they basically never be mixed (unless you _really_ know what you're doing). – ggorlen Mar 21 '23 at 00:47
  • Oh, I definitely agree with that! It's just that a major part of this answer is talking about the exceptions so I thought I'd mention another case – Bergi Mar 21 '23 at 03:33
2

there is no harm, but it's more code and harder to read, you can do this instead:

async apiCall(params) {
 try{
   var results = await this.anotherCall()
    return results;
 } catch(err){
   //do something with the error
 }
}

although I'm not sure what you are trying to do here, but returning the results in the try block only means that the function may return undefined in error cases.

also it's important to remember that async functions always return a promise, so to use the apiCall function you have 2 ways of doing it:

// 1- using .then to access the returned value
apiCall().then(results => {
   if(results){
      //doing something with the results
  }
})

// 2- awaiting the function inside another async function
async anotherFunction(){
  const results = await apiCall();
  if(results){
      //doing something with the results
  }
}

and using if(results) ensures that you are dealing with something other than undefined

Ma0meDev
  • 29
  • 2
  • "there is no harm, but it's more code and harder to read" ... "more code and harder to read" seems like harm. It tends to result in bugs. Why were `if`s added to OP's code? Most of the code here is unnecessary. – ggorlen Mar 19 '23 at 19:25
-1

Simply

async apiCall(params) {
var results = await this.anotherCall()
  .then(async (results) => {
    //do any results transformations
    await //your action
    return results;
  })
  .catch(error => {
    //handle any errors here
  });
return results;
}
-4

Just to add to this set of answers I always use await with then to make code more readable like so(aws example):

I found this code snippet years ago(to function), probably somewhere on stack, I just can't remember where!

async function myFunction(){
  var params = ....
  
  // either err will be null and data will have a value or if an exception was caused then data will be null and err will contain the information from the catch.
  let [err, result] = await to(route53.changeResourceRecordSets(params).promise())

  if(err){

    // handle the error
  }

  // carry on
}

to(promise){ // dunno why I called "to" call it anything you like.
  return promise.then((data) => {
  return [null, data]
 }).catch(err => [err])
}


Mrk Fldig
  • 4,244
  • 5
  • 33
  • 64