When working with TypeScript, one of the most common confusions is around asyncfunctions 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
trueorfalse, the value is wrapped in aPromise.
❓Why Promise and not boolean?
Every asyncfunction 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.
-
awaitpauses execution only in the current function until thePromiseresolves. - But it does not block the event loop. Other tasks can keep running.
So:
-
Asyncfunctions are still asynchronous under the hood. -
awaitjust 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
asyncworks 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 asyncoften 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
-
asyncfunctions always returnPromise<T>, never justT. - Returning a raw value auto-wraps it in
Promise.resolve(value). -
awaitpauses locally but doesn’t block the event loop. - Async arrow functions are common in callbacks, handlers, and array methods.
- Use
asyncwhen dealing with APIs, databases, or any future async work — it future-proofs your function signatures.
Top comments (1)
Thanks for sharing this. I have learnt that async functions in TypeScript always return Promise, I never knew this before, and wondered why this is in some code bases I have seen it in