When working with TypeScript, one of the most common confusions is around async
functions and their return types. Let’s clear this up with a practical example.
📝A Practical Example: Checking If a User Is Logged In
Imagine you’re building an app where you need to check if a user is logged in.
Synchronous version
function isUserLoggedIn(): boolean {
return true;
}
This works fine if the answer is always available instantly.
Asynchronous version
But in real-world apps, login status often comes from a server or database:
async function isUserLoggedIn(): Promise<boolean> {
const response = await fetch("/api/check-login");
if (!response.ok) {
return false;
}
const data = await response.json();
return data.loggedIn; // true or false
}
Notice:
- The return type is
Promise<boolean>
(notboolean
). - Even though we return
true
orfalse
, the value is wrapped in aPromise
.
❓Why Promise and not boolean?
Every async
function in TypeScript always returns a Promise
.
So this:
return true;
is really:
return Promise.resolve(true);
That’s why the return type must be Promise<boolean>
.
If you mistakenly annotate it as just boolean
:
async function isUserLoggedIn(): boolean {
return true;
}
You’ll get a compile error:
Type 'boolean' is not assignable to type 'Promise<boolean>'.
📝Using the Async Function
// With await
const loggedIn = await isUserLoggedIn();
console.log(loggedIn); // true or false
// Or with .then()
isUserLoggedIn().then(status => console.log(status));
⚠️Misconception: Isn’t This Synchronous Because of await?
A common doubt: “If we’re awaiting, aren’t we back to synchronous execution?”
Answer: No.
-
await
pauses execution only in the current function until thePromise
resolves. - But it does not block the event loop. Other tasks can keep running.
So:
-
Async
functions are still asynchronous under the hood. -
await
just gives you synchronous-looking syntax for cleaner code.
Example:
async function main() {
console.log("Before");
await isUserLoggedIn(); // pauses *this* function only
console.log("After");
}
main();
console.log("Outside");
Output order:
Before
Outside
After
Notice how "Outside" runs while we were “waiting.” That’s the power of async/await.
📌Async Arrow Functions
async
works seamlessly with arrow functions. This is very common in modern TypeScript and React projects.
Example with arrow function
const isUserLoggedIn = async (): Promise<boolean> => {
const response = await fetch("/api/check-login");
const data = await response.json();
return data.loggedIn;
};
In a React handler
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const success = await isUserLoggedIn();
console.log("Login status:", success);
};
With array methods
Arrow functions and async
often pair with array operations:
const urls = ["/a", "/b", "/c"];
const fetchAll = async () => {
const results = await Promise.all(
urls.map(async url => {
const res = await fetch(url);
return res.json();
})
);
console.log(results);
};
💡Key Takeaways
-
async
functions always returnPromise<T>
, never justT
. - Returning a raw value auto-wraps it in
Promise.resolve(value)
. -
await
pauses locally but doesn’t block the event loop. - Async arrow functions are common in callbacks, handlers, and array methods.
- Use
async
when dealing with APIs, databases, or any future async work — it future-proofs your function signatures.
Top comments (0)