Reputation: 1241
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
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):
browser.newPage is not a function
using puppeteer-core?And related threads:
Upvotes: 18
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
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
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 await
ing.
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 await
ing 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
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
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
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
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
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
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
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
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
Reputation: 5202
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