using async await and .then together
Asked Answered
A

13

92

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;
  }
Annabelannabela answered 6/3, 2019 at 9:27 Comment(1)
no, there is no harm. You are only passing the final value of the promise chain to the result variable using await.Gurley
H
28

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

Hyperspace answered 6/3, 2019 at 9:40 Comment(3)
Harm, the way I see it, is: does the interpreter throw an exception? Is this bad code? — It isn't.Adiell
In this example there is no code that used both await and then togetherMyrtia
why will you use await and then together ? @Myrtia they do the same thing right ?Hyperspace
E
78

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();
Enrika answered 6/3, 2019 at 13:35 Comment(8)
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.Coverley
Let's say you have more code after the result = line. Let's say that there was an error. Is result then undefined?Strabismus
@JeffPadgett Yes, you can return a fallback value from the .catch() methodEnrika
Do you mind updating your answer with a fallback from the .catch() method?Strabismus
@JeffPadgett Updated answer.Enrika
Really nice and compact, the fallback value is a very good way to avoid Typescript errorAntisana
your code doesn't include any then clause at the same time of using await, which is exactly what the question is about!Ironbound
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.Ashien
M
50

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.

Meimeibers answered 2/6, 2021 at 18:9 Comment(4)
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?Fancher
@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.Meimeibers
Actually there is an excellent point, relating to decomposition and modularization. Reusable async calls can be shared across contexts, so you want to have a promise.then() in 1 context, and await it in another. That way your API module can do a whole bunch of things to post-process the data, and the caller can just await the end promise, ignorant of the details.Taub
This seems like a very strong illustration of why it's dangerous and confusing to mix await and then, but it'd be nice if it stated it a bit more explicitly. Just don't mix the two and the footgun disappears.Ashien
H
28

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

Hyperspace answered 6/3, 2019 at 9:40 Comment(3)
Harm, the way I see it, is: does the interpreter throw an exception? Is this bad code? — It isn't.Adiell
In this example there is no code that used both await and then togetherMyrtia
why will you use await and then together ? @Myrtia they do the same thing right ?Hyperspace
A
15

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:

Ashien answered 19/3, 2023 at 22:9 Comment(3)
"An exception I agree with" - see also my answer on try-catch syntax in async/await. I do actually also like using .then() with two handlers instead of .catch()- it would also be appropriate in your first snippet, for exampleSubsolar
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).Ashien
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 caseSubsolar
F
12

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.

Florella answered 15/7, 2021 at 4:27 Comment(2)
Suggestion : add code fences and the language identifier to highlight the code and make it more readable.Protoactinium
I agree that mixing await and then harms readability but I don't understand your example here.Ashien
M
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!"));

Mephitic answered 6/3, 2019 at 9:55 Comment(2)
i use that all the time and it's better than using .then + another anonymous async function that makes the whole thing illegibleLull
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.Ashien
F
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

Floorboard answered 5/9, 2022 at 9:2 Comment(1)
"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 ifs added to OP's code? Most of the code here is unnecessary.Ashien
T
0

Is there technical harm? No. But, it's kinda non-standard syntax to mix them in that particular way, and some might say your example would be harmful merely as inconsistent style. Better to train people to standardize on the right tool for the right job, and in your example, I think sticking with await is the way to go.

BUT... there's an excellent and common use-case for "using" promise.then and async/await together; it's just the reverse flow of your example. The whole point of await is that it awaits and unwraps... a promise, right? That promise must come from somewhere, right? On the other side of a module boundary, perhaps? It's an excellent pattern to use .then on that other side, and then consume it with await. You're not "using them together" within a single function. That would be ugly. But you are "using them together" in terms of your whole application ... Ex.

// MODULE-1
export async function apiCall(params) {
    return apiInstance.someRequest(params)
      .then(results => {
        //do any results transformations
        return processedResults;
      })
      .catch(error => {
        //handle any errors here
      });
  }

// MODULE-2
import { apiCall } from 'MODULE-1'

async function consumeApi() {
    ... do something
    const processedResults = await apiCall()
    .... do something else
}

Taub answered 7/9, 2023 at 1:48 Comment(5)
I don't understand the difference between apiCall and consumeApi. At the end of the day, both of them are consumers of promises. Ultimately apiInstance.someRequest is the originator, assuming that's the third-party promise-based API (almost always, application code doesn't originate the promise, whatever library exposes I/O or HTTP does). In most cases I'd want to hoist error handling to the higher-level consumer.Ashien
apiCall produces a promise which can be returned to consumer, consumeApi. consumeApi can use await only because apiCall chose to return the core promise, rather than eating it. Many ways to do it, but one is promise.then(), as discussed. apiCall is where we compose and orchestrate the interaction with the core promise-producing library, so that composition doesn't have to be exposed down at the calling level, mixing concerns. Re: error-handling... in an ideal world you want both: central handling (tokens, logging, etc), and local handling (exposing some errors to the user).Taub
"only because apiCall chose to return the core promise, rather than eating it" there's no way to eat a promise--all code all the way up is asynchronous once you call a promise-based function. Both async/await and then/catch can deal with it, but neither eats the promise and turns it into sync code. The difference between async/await and then/catch is basically cosmetic.Ashien
I think you may be conflating the concepts of Promise and async-ness. Yes, an async function returns a promise, but not necessarily the same promise that it originally consumed. Doing so is a choice, to provide a persistent representation of the state of a very specific operation, one which can easily be passed through multiple other contexts, and used by all of them, both before and after the operation has completed. This is the distinction. You don't have to unwrap a promise immediately. You can receive it, await its result, then do other things with it, such as pass it to others...Taub
I still don't follow the distinction. Anyway, it's largely tangential to OP's question, since they're asking about mixing the two options for waiting for promises to resolve within one function. At least we can agree not to do that in most cases.Ashien
R
0

Lets focus on differences of the await and then first to get to understanding of the reasonability to intermixing those both.

await pauses chain of asynchronous semantical execution until completing the argument asynchronous call. Which means that current async function does not finish its semantical execution but allows to process message queue with other running async calls and tasks during the awaiting.

The then is the way to have current function body to completely end its execution and return, so other calls may be started in the async call graph without awaiting for result but instead attach callback with lambda that closure's everything required to accomplish execution logic in the context of finishing thread.

If you need to await before continue of the async chain and you need to complete the then lambda before continue current async call chain, before returning from the async function context then putting await on the then is semantically equivalent to splitting the statement in two awaits without need to closure for the then parameter. Using await once for async call and second for then param instead of the then makes the code more readable because of multi-lining and less closures.

Therefore semantically there is no much of reason in intermixing both in this case.

But if your function did all other tasks and has no reason to await for completion before semantical returning then there is no much reason for using the await keyword and then/catch leads to better async graph resolution in terms of performance.

Summary: it depends. Do you have await that follows in the function body? If you do then probably await per statement is better because of readability. If there is a way to closure anything required and use then chain without await then async function may semantically return instantly to continue on other calls and better to not use any await at all. But hardly using both at the same time make any sense to do.

Rosalie answered 19/10, 2023 at 14:42 Comment(0)
A
-2

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;
}
Acceptance answered 27/6, 2023 at 20:33 Comment(0)
K
-2

I do believe that it can be useful in some cases and just annoying in others, a case that it would be useful is in calling a Promise that returns a Promise. For example the Fetch API:

So instead of doing this:

const res = await fetch('someUrl');
const jsonRes = await res.json();

You can just do this:

const res = await fetch('someUrl').then(res => res.json());

Which is more compact and even easier to read.

Krohn answered 12/12, 2023 at 3:45 Comment(3)
It still lacks error handling though, for which the await version is more suitableSubsolar
Well you can still add a ‘catch’ statement or a ‘try/catch’Krohn
No, I mean handling of http errors, please see the linked postSubsolar
D
-2

No harm being done whatsoever. But you do need to understand how await, then, catch, try, catch and how promises work in general.

Actually I find it very useful in cases where you have multiple async operations and you want to handle their potential errors separately.

// using await here makes sure we're connected to the database
// before trying to insert any data into it
const { db } = await DatabaseConnection()
  .then((r) => r)
  .catch((err) => console.error("unable to connect"));

await db.collection("users").insertOne("exampleuser")
  .then(() => console.log("user inserted"))
  .catch((err) => console.error("couldn't insert user"))

// you can be 100% sure this operation executes after  the one above finishes executing
// that would not be true if you weren't using await
const user = await db.collection("users").find("exampleuser")
  .then((user) => user)
  .catch((err) => {
    console.error("couldn't find user");
    return null;
  })

In my view, it's a nicer alternative to .then() chaining or god forbid using try, catch.

Dekaliter answered 18/12, 2023 at 2:41 Comment(0)
A
-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])
}


Armillary answered 7/9, 2022 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.