React + TypeScript: async functions



This content originally appeared on DEV Community and was authored by Arka Chakraborty

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> (not boolean).
  • Even though we return trueor false, the value is wrapped in a Promise.

❓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 the Promiseresolves.
  • 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 return Promise<T>, never just T.
  • 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.


This content originally appeared on DEV Community and was authored by Arka Chakraborty