Be Careful with Async Functions that Return Booleans
source link: https://www.aleksandrhovhannisyan.com/blog/dev/async-functions-that-return-booleans/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Be Careful with Async Functions that Return Booleans
Here’s a fun bug I recently encountered… Let’s say we have this async
JavaScript function:
const isBroken = async () => {
return false;
}
if (isBroken()) {
throw new Error("Oopsie woopsie");
}
I’ve kept the code simple for this post; in reality, you’ll probably fetch data asynchronously in the function (otherwise, there’s no need to mark it as async
):
const isBroken = async () => {
const x = await y();
// ...
// ... more stuff here maybe
// ...
return x === 42;
}
if (isBroken()) {
throw new Error("Oopsie woopsie");
}
Either way, does the body of the if
statement execute? Take a second to think this through. You can check if you’re right by copying the first code sample and executing it in your dev tools.
(You can probably already guess the answer, though, just based on the fact that I’m writing this post.)
Async Functions, Booleans, and Truthiness: A Slippery Bug
The if
statement in this code will always execute its body, regardless of what the async function returns. ESLint won’t flag this as an error. TypeScript technically should, but it has not yet implemented this warning for un-awaited async functions.
To understand why the expression always evaluates to true
, recall that async/await
is just syntactic sugar for Promises. In JavaScript, an async
function actually wraps its return value in a Promise
object—even if it seems like the function is directly returning a value, and even if the function does not await
anything.
We can verify this by logging the function call:
> console.log(isBroken())
Promise {<fulfilled>: false}
Our async function’s return value is not false
itself but rather a Promise object that resolved with the value false
. In other words, it’s the same as doing this:
const isBroken = () => {
return Promise.resolve(false);
}
if (isBroken()) {
throw new Error("Oopsie woopsie");
}
Spot the problem? This is an issue of truthiness: a Promise
object is not one of the eight falsy values in JavaScript, so checking its truthiness is pointless: When coerced to a boolean, a Promise is always true
.
For the code above to work as intended, you’ll need to await
the result in another async
function (or, equivalently, chain a .then
call on the returned Promise object):
const isBroken = async () => {
return false;
}
const foo = async () => {
const somethingIsWrong = await isBroken();
if (somethingIsWrong) {
throw new Error("Oopsie woopsie");
}
}
This is a pretty interesting bug that you may run into, though some basic tests will probably catch it before you need to go looking for it yourself. However, in the absence of tests, unless you remember that the function is async (and that async functions return Promises), this bug could easily slip right past you.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK