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));
}
▶ Try it Yourself

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

📝 Exercises

  1. 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."
  2. 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.
  3. 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).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏