PixelPaul
PixelPaul

Reputation: 1241

using async await and .then together

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;
  }

Upvotes: 98

Views: 152837

Answers (13)

ggorlen
ggorlen

Reputation: 56983

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:

Upvotes: 18

Aram Poghosyan
Aram Poghosyan

Reputation: 57

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.

Upvotes: -2

Emad Abdelmaksoud
Emad Abdelmaksoud

Reputation: 92

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.

Upvotes: -2

Serg Kryvonos
Serg Kryvonos

Reputation: 4677

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.

Upvotes: 0

XML
XML

Reputation: 19498

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
}

Upvotes: 0

Ragavendhran N
Ragavendhran N

Reputation: 47

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;
}

Upvotes: -2

Mrk Fldig
Mrk Fldig

Reputation: 4486

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])
}


Upvotes: -4

Ma0meDev
Ma0meDev

Reputation: 29

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

Upvotes: 2

Bricky
Bricky

Reputation: 2745

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.

Upvotes: 54

PeterYuan
PeterYuan

Reputation: 127

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.

Upvotes: 11

Lin Du
Lin Du

Reputation: 102327

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();

Upvotes: 82

Shubham Dixit
Shubham Dixit

Reputation: 1

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

Upvotes: 30

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!"));

Upvotes: 9

Related Questions