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:
result
variable usingawait
. – Gurley