Any function marked async automatically returns a Promise wrapping its stated or inferred return type — writing async function f(): Promise<string> is equivalent to writing async function f(): string, since TypeScript wraps it either way. Inside another async function, await unwraps that Promise back to its inner type, and TypeScript tracks this transformation precisely at every step, catching an accidental double-wrap or a missing await.
async functions always return a Promise
Writing the return type as Promise<User> or just User produces the identical compiled behavior — TypeScript infers the Promise wrapper automatically for any async function.
interface User { name: string; }
async function fetchUser(): Promise<User> {
const response = await fetch("/api/user");
return response.json(); // TypeScript trusts json()'s declared type here
}
async function caller() {
const user = await fetchUser(); // awaited: user is User, not Promise<User>
console.log(user.name);
}Typing Promise.all and concurrent operations
Promise.all's return type is inferred as a tuple matching each input promise's resolved type — TypeScript tracks each position individually, even when the promises resolve to different types.
async function fetchUser(): Promise<{ name: string }> { /* ... */ return { name: "Alice" }; }
async function fetchPosts(): Promise<string[]> { /* ... */ return ["post1"]; }
async function loadDashboard() {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
// user: { name: string }, posts: string[] — each correctly typed, not "any"
}Errors: catch variables are unknown, not any
Since TypeScript 4.4, a caught error defaults to unknown rather than any under strict settings — forcing an explicit type check before accessing any property on it, since JavaScript allows throwing literally any value, not just Error objects.
async function loadData() {
try {
return await fetchUser();
} catch (err) {
// err is unknown — must narrow before use
if (err instanceof Error) {
console.error(err.message);
}
throw err;
}
}