TypeScript: Asynchronous Programming and Types in TypeScript
Last updated: 2026-08-26
Asynchronous programming is a core pattern in JavaScript—TypeScript adds full type support for asynchronous operations through generic Promises and async/await.
1. Basics of the Promise Type
(1) Generic Types of Promises
A Promise is a generic type—Promise<T> indicates that "a value of type T will be produced in the future":
TYPESCRIPT
// Synchronization Functions——Direct Return Value
function getUserSync(): string {
return "Charlie";
}
// Asynchronous Functions——Back Promise<string>
function getUserAsync(): Promise<string> {
return Promise.resolve("Charlie");
}
// Simulate Web Requests
function fetchUser(id: number): Promise<{ id: number; name: string }> {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, name: "User" + id }), 100);
});
}
(2) Type inference in then and catch
TYPESCRIPT
let promise: Promise<string> = Promise.resolve("hello");
promise
.then(value => {
// value Type automatically inferred as string
console.log(value.toUpperCase()); // ✅
return value.length; // Back number → then Chain becomes Promise<number>
})
.then(length => {
// length Type automatically inferred as number
console.log(length.toFixed(2)); // ✅
})
.catch(error => {
// error Type: any(TypeScript Unable to infer the error type)
console.log(error.message);
});
(3) Immutability and Safety of the Promise Type
TYPESCRIPT
// Promise It is covariant.——Promise<string> Can be assigned to Promise<string | number>
let p1: Promise<string> = Promise.resolve("hello");
let p2: Promise<string | number> = p1; // ✅ Covariant Safety
// It doesn't work the other way around.
// let p3: Promise<string> = p2; // ❌ Promise<string | number> Cannot be assigned Promise<string>
▶ Example: Type-Safe API Client
Output:
TEXT
📖 Display only
User: User1
Number of Articles: 1
Number of comments: 1
TYPESCRIPT
interface ApiResponse<T> {
status: number;
data: T;
message: string;
}
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
title: string;
price: number;
}
function apiGet<T>(url: string): Promise<ApiResponse<T>> {
// Simulation API Call
return new Promise((resolve) => {
setTimeout(() => {
resolve({
status: 200,
data: {} as T, // In actual projects, this is done by JSON.parse Result Assertion
message: "OK"
});
}, 100);
});
}
// Usage——Each request has a specific return type
async function getUser(id: number): Promise<User> {
let response = await apiGet<User>(`/api/users/${id}`);
return response.data;
}
async function getProducts(): Promise<Product[]> {
let response = await apiGet<Product[]>("/api/products");
return response.data;
}
Output:
TEXT
📖 Display only
No runtime output — demonstrates typed API client pattern
2. Types in async/await
(1) Return Values of async Functions
Async functions always return a Promise—even if you return a regular value, TypeScript automatically wraps it:
TYPESCRIPT
// Explicit Return Values——Automatic packaging is Promise
async function greet(): Promise<string> {
return "Hello"; // Equivalent to return Promise.resolve("Hello")
}
// Explicit Return Promise——No double packaging
async function fetchName(): Promise<string> {
return Promise.resolve("Charlie"); // It won't turn into Promise<Promise<string>>
}
(2) Type Inference with await
await unwraps Promise — await Promise<T> 's type is T:
TYPESCRIPT
async function example() {
let name: string = await Promise.resolve("Charlie"); // ✅ Unpack to string
let count: number = await Promise.resolve(42); // ✅ Unpack to number
let user: User = await fetchUser(1); // ✅ Unpack to User
}
(3) await Pitfall — May Be Rejected
TYPESCRIPT
async function riskyOperation(): Promise<number> {
// This Promise possibly reject
let result = await mayFail(); // result The type is number——However, an exception may be thrown during runtime.
return result;
}
async function mayFail(): Promise<number> {
if (Math.random() > 0.5) {
throw new Error("Random failure");
}
return 42;
}
// Safe coding practice — use try/catch
async function safeOperation(): Promise<number | null> {
try {
let result = await mayFail();
return result;
} catch {
return null;
}
}
3. Promise Utility Types
(1) Awaited—Unwrapping a Promise Type
TypeScript 4.5 includes the Awaited<T> type—recursive unwrapping of Promises:
TYPESCRIPT
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number(Recursive Unpacking)
type C = Awaited<string>; // string (non-Promise, returned directly)
type D = Awaited<Promise<string | number>>; // string | number
(2) Practical Use—Extracting the Return Type of an Asynchronous Function
TYPESCRIPT
async function fetchUser(id: number) {
let response = await fetch(`/api/users/${id}`);
return response.json() as Promise<{ id: number; name: string }>;
}
// Extracting the Return Type of an Asynchronous Function(Remove Promise Packaging)
type UserResponse = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string }
(3) Custom Promise Utility Types
TYPESCRIPT
// Extract Promise Each element in the array Promise Parsed Type
type UnwrapPromiseArray<T> = {
[K in keyof T]: T[K] extends Promise<infer U> ? U : T[K];
};
type Input = [Promise<string>, Promise<number>, boolean];
type Output = UnwrapPromiseArray<Input>;
// [string, number, boolean]
4. Type Safety in Concurrency Control
(1) Promise.all
TYPESCRIPT
async function loadDashboard() {
// Promise.all Accepting different types of Promise Array
let [users, products, stats] = await Promise.all([
fetchUsers(), // Promise<User[]>
fetchProducts(), // Promise<Product[]>
fetchStats() // Promise<Stats>
]);
// Each variable has its own type.
console.log(users.length); // User[].length
console.log(products[0].title); // Product.title
console.log(stats.totalUsers); // Stats.totalUsers
}
(2) Promise.allSettled
TYPESCRIPT
type SettledResult<T> = {
status: "fulfilled";
value: T;
} | {
status: "rejected";
reason: unknown;
};
async function tryMultipleApis() {
let results = await Promise.allSettled([
fetchFromApi1(), // Promise<User>
fetchFromApi2(), // Promise<User>
]);
for (let result of results) {
if (result.status === "fulfilled") {
console.log(result.value.name); // ✅ value Type: User
} else {
console.log(result.reason); // unknown
}
}
}
(3) Promise.race with Promise.any
TYPESCRIPT
// race——Take the result that was completed first(Whether success or failure)
async function fetchWithTimeout(url: string, ms: number) {
let result = await Promise.race([
fetch(url),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
)
]);
return result;
}
// any——Take the first successful result(Ignore Failure)
async function fetchWithFallback() {
let result = await Promise.any([
fetchFromPrimary(), // Priority
fetchFromSecondary(), // Standby
]);
// result Type: All Promise composite types' resolved values
}
▶ Example: Concurrent Data Loader
Output:
TEXT
📖 Display only
User1 has 1 post(s)
TYPESCRIPT
interface User { id: number; name: string; }
interface Post { id: number; title: string; authorId: number; }
interface Comment { id: number; postId: number; text: string; }
async function fetchAllData(userId: number) {
let [user, posts, comments] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchComments(userId)
]);
return { user, posts, comments };
}
async function fetchUser(id: number): Promise<User> {
return { id, name: "User" + id };
}
async function fetchPosts(userId: number): Promise<Post[]> {
return [{ id: 1, title: "Article1", authorId: userId }];
}
async function fetchComments(userId: number): Promise<Comment[]> {
return [{ id: 1, postId: 1, text: "Great Article!" }];
}
async function main() {
let data = await fetchAllData(1);
console.log(`User:${data.user.name}`);
console.log(`Number of Articles:${data.posts.length}`);
console.log(`Number of comments:${data.comments.length}`);
}
main();
Output:
TEXT
📖 Display only
User:User1
Number of Articles:1
Number of comments:1
5. Asynchronous Iterators
(1) AsyncIterable with for await...of
TYPESCRIPT
async function* asyncCounter(max: number): AsyncGenerator<number> {
for (let i = 0; i < max; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
async function run() {
for await (let num of asyncCounter(3)) {
console.log(num);
}
// 0 → 1 → 2(Every interval100ms)
}
(2) Types of Asynchronous Generators
TYPESCRIPT
interface AsyncGenerator<T> {
next(): Promise<IteratorResult<T>>;
return(value?: any): Promise<IteratorResult<T>>;
throw(e?: any): Promise<IteratorResult<T>>;
[Symbol.asyncIterator](): AsyncGenerator<T>;
}
▶ Example: Awaited Utility Type in Practice
TYPESCRIPT
interface User { id: number; name: string; }
interface Post { id: number; title: string; }
async function fetchUser(id: number): Promise<User> {
return { id, name: "User" + id };
}
async function fetchPosts(userId: number): Promise<Post[]> {
return [{ id: 1, title: "Post by " + userId }];
}
type UserType = Awaited<ReturnType<typeof fetchUser>>; // User
type PostsType = Awaited<ReturnType<typeof fetchPosts>>; // Post[]
function render(user: UserType, posts: PostsType): string {
return `${user.name} has ${posts.length} post(s)`;
}
async function main() {
let user = await fetchUser(1);
let posts = await fetchPosts(user.id);
console.log(render(user, posts));
}
Output:
TEXT
📖 Display only
User1 has 1 post(s)
❓ FAQ
Q Does an
async function have to return a Promise?A It’s recommended to explicitly specify it. TypeScript can infer that an
async function returns a Promise, but explicitly specifying it async function fn(): Promise<T> provides greater clarity—it helps with documentation and catches errors at compile time. You can omit the return type for simple functions, but it’s recommended to specify it for complex functions.Q Why is the error type "unknown" in a Promise's catch block?
A Because JavaScript allows any value to be thrown (not limited to Error), TypeScript cannot guarantee that the value caught will be of type Error. It is recommended to perform a type check in the catch block:
if (error instanceof Error) or use a type assertion: error as Error.Q What happens if one of the Promises in
Promise.all fails?A
Promise.all operates on an "all-or-nothing" basis—if any single Promise rejects, the entire all operation rejects. If you need "all to complete (regardless of success or failure)," use Promise.allSettled. If you need "the first to succeed," use Promise.any.Q Can asynchronous functions be overloaded?
A Yes. Overloading asynchronous functions works exactly the same as with synchronous functions—overloaded signatures describe the return types for different parameter combinations (Promise wrappers), and the implementation ensures signature compatibility across all overloads.
📖 Summary
Promise<T>is a type of asynchronous operation—T is a value type that will be generated in the future- Async functions always return a Promise;
awaitunwraps the Promise to get T Awaited<T>Recursively Unwrapping Promise Types—Extracting the Return Type of Asynchronous Functions- Promise.all executes multiple Promises concurrently and returns a tuple (with each element of a different type)
- Promise.allSettled waits for all, Promise.race takes fastest, Promise.any takes first success
- Asynchronous generator
AsyncGenerator<T>works withfor await...ofto implement asynchronous iteration
📝 Exercises
- Basic Problem (Difficulty ⭐): Write a function named
delay(ms: number): Promise<void>that returns a Promise that resolves after ms milliseconds. Use async/await to call it and implement "print a message after waiting 1 second." - Advanced Problem (Difficulty ⭐⭐): Write a
retry<T>(fn: () => Promise<T>, maxRetries: number): Promise<T>function—when fn fails, it automatically retries up to maxRetries times. Implement this using try/catch and a loop. - Challenge Problem (Difficulty ⭐⭐⭐): Implement
concurrentLimit<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]>—execute tasks concurrently, but run no more than limit tasks at the same time. Once all tasks are complete, return an array of results (in the same order).