Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Typescript
  4. /Async Await Promises
Your Progress0%
0 of 30 completed

TypeScript Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices

Async/Await and Promises

Typing asynchronous code with Promises and async/await

Asynchronous programming is essential in modern JavaScript development. TypeScript provides excellent support for typing Promises and async/await patterns, ensuring type safety even in asynchronous operations. Let's master async TypeScript!

Promise Basics

A Promise represents a value that may be available now, in the future, or never. TypeScript allows you to type the resolved value.

promise-basics.ts
// Creating a typed Promise
function delay(ms: number): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

// Promise with typed resolve value
function fetchNumber(): Promise<number> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const random = Math.random();
      if (random > 0.5) {
        resolve(42);
      } else {
        reject(new Error("Random number too low"));
      }
    }, 1000);
  });
}

// Using Promises with .then()
fetchNumber()
  .then((num: number) => {
    console.log("Got number:", num);
    return num * 2;
  })
  .then((doubled: number) => {
    console.log("Doubled:", doubled);
  })
  .catch((error: Error) => {
    console.error("Error:", error.message);
  });

// Promise with custom type
interface User {
  id: number;
  name: string;
  email: string;
}

function fetchUser(id: number): Promise<User> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({
          id,
          name: "Yusuf Ibrahim",
          email: "yusuf@example.com"
        });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 500);
  });
}

// Using the typed Promise
fetchUser(1)
  .then((user: User) => {
    console.log(`User: ${user.name}`);
    console.log(`Email: ${user.email}`);
  })
  .catch((error: Error) => {
    console.error("Failed to fetch user:", error.message);
  });

// Promise.resolve and Promise.reject
const resolvedPromise: Promise<string> = Promise.resolve("Success");
const rejectedPromise: Promise<never> = Promise.reject(new Error("Failed"));

resolvedPromise.then(value => console.log(value));  // "Success"
rejectedPromise.catch(error => console.error(error));

Async Functions

Async functions always return a Promise. The return type should be wrapped in Promise<T>.

async-functions.ts
// Basic async function
async function greet(): Promise<string> {
  return "Hello, World!";
}

// Equivalent to:
function greetPromise(): Promise<string> {
  return Promise.resolve("Hello, World!");
}

// Async function with await
async function fetchData(): Promise<string> {
  await delay(1000);
  return "Data fetched";
}

// Type inference works
async function getNumber(): Promise<number> {
  const num = 42;  // TypeScript knows this is number
  return num;      // Automatically wrapped in Promise
}

// Using the async function
async function main() {
  const greeting = await greet();
  console.log(greeting);  // Type: string
  
  const data = await fetchData();
  console.log(data);  // Type: string
}

// Async function with parameters
async function calculateSum(a: number, b: number): Promise<number> {
  // Simulate async operation
  await delay(100);
  return a + b;
}

calculateSum(5, 3).then(result => {
  console.log("Sum:", result);  // Type: number
});

// Async function with error handling
async function riskyOperation(): Promise<string> {
  const random = Math.random();
  
  if (random < 0.5) {
    throw new Error("Operation failed");
  }
  
  return "Success";
}

async function safeCall() {
  try {
    const result = await riskyOperation();
    console.log(result);
  } catch (error) {
    console.error("Error:", (error as Error).message);
  }
}

// Async function returning void
async function logMessage(message: string): Promise<void> {
  await delay(500);
  console.log(message);
  // No return statement needed for Promise<void>
}

// Helper function from earlier
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

💡 Async Functions Always Return Promises

Even if you return a plain value from an async function, it's automatically wrapped in a Promise. The return type must always be Promise<T>, never just T.

Typing Fetch Requests

fetch-api.ts
// Define response types
interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
}

// Basic fetch with typing
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`https://api.example.com/users/${id}`);
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const user: User = await response.json();
  return user;
}

// Fetch with generic API response
async function fetchUsers(): Promise<User[]> {
  const response = await fetch('https://api.example.com/users');
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const apiResponse: ApiResponse<User[]> = await response.json();
  
  if (!apiResponse.success) {
    throw new Error(apiResponse.message || "Request failed");
  }
  
  return apiResponse.data;
}

// POST request with typed body and response
interface CreateUserDto {
  name: string;
  email: string;
  password: string;
}

async function createUser(userData: CreateUserDto): Promise<User> {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData)
  });
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const user: User = await response.json();
  return user;
}

// Generic fetch function
async function fetchApi<T>(
  url: string,
  options?: RequestInit
): Promise<T> {
  const response = await fetch(url, options);
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const data: T = await response.json();
  return data;
}

// Using the generic function
async function loadData() {
  // TypeScript knows users is User[]
  const users = await fetchApi<User[]>('https://api.example.com/users');
  
  // TypeScript knows user is User
  const user = await fetchApi<User>('https://api.example.com/users/1');
  
  console.log(users[0].name);
  console.log(user.email);
}

// Error handling wrapper
async function safeFetch<T>(
  url: string,
  options?: RequestInit
): Promise<{ data: T | null; error: string | null }> {
  try {
    const data = await fetchApi<T>(url, options);
    return { data, error: null };
  } catch (error) {
    return {
      data: null,
      error: (error as Error).message
    };
  }
}

// Using safe fetch
async function getUserSafely(id: number) {
  const { data, error } = await safeFetch<User>(
    `https://api.example.com/users/${id}`
  );
  
  if (error) {
    console.error("Error:", error);
    return;
  }
  
  console.log("User:", data?.name);
}

Promise Combinators

promise-combinators.ts
interface User {
  id: number;
  name: string;
}

interface Product {
  id: number;
  name: string;
  price: number;
}

interface Order {
  id: number;
  userId: number;
  productIds: number[];
}

// Promise.all - wait for all promises
async function loadAllData(): Promise<void> {
  const [users, products, orders] = await Promise.all([
    fetchApi<User[]>('/api/users'),
    fetchApi<Product[]>('/api/products'),
    fetchApi<Order[]>('/api/orders')
  ]);
  
  console.log(`Loaded ${users.length} users`);
  console.log(`Loaded ${products.length} products`);
  console.log(`Loaded ${orders.length} orders`);
}

// Promise.all with mixed types
async function loadMixedData(): Promise<[User[], number, string]> {
  return Promise.all([
    fetchApi<User[]>('/api/users'),
    Promise.resolve(42),
    Promise.resolve("hello")
  ]);
}

const [users, count, message] = await loadMixedData();
// users: User[]
// count: number
// message: string

// Promise.race - first to complete
async function fetchWithTimeout<T>(
  url: string,
  timeoutMs: number
): Promise<T> {
  const fetchPromise = fetchApi<T>(url);
  
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Request timeout after ${timeoutMs}ms`));
    }, timeoutMs);
  });
  
  return Promise.race([fetchPromise, timeoutPromise]);
}

// Usage
async function loadUserWithTimeout() {
  try {
    const user = await fetchWithTimeout<User>('/api/users/1', 5000);
    console.log("User loaded:", user);
  } catch (error) {
    console.error("Failed or timed out:", error);
  }
}

// Promise.allSettled - wait for all, get all results
async function loadAllWithStatus() {
  const results = await Promise.allSettled([
    fetchApi<User[]>('/api/users'),
    fetchApi<Product[]>('/api/products'),
    fetchApi<Order[]>('/api/orders')
  ]);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Request ${index} succeeded:`, result.value);
    } else {
      console.error(`Request ${index} failed:`, result.reason);
    }
  });
}

// Promise.any - first successful
async function loadFromMirrors<T>(urls: string[]): Promise<T> {
  const promises = urls.map(url => fetchApi<T>(url));
  return Promise.any(promises);
}

// Usage
async function loadUserFromMirrors() {
  const user = await loadFromMirrors<User>([
    'https://api1.example.com/users/1',
    'https://api2.example.com/users/1',
    'https://api3.example.com/users/1'
  ]);
  
  console.log("User loaded from fastest mirror:", user);
}

// Helper function
async function fetchApi<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

Error Handling Patterns

error-handling.ts
// Custom error types
class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public response?: any
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

// Fetch with detailed error handling
async function fetchWithErrorHandling<T>(url: string): Promise<T> {
  let response: Response;
  
  try {
    response = await fetch(url);
  } catch (error) {
    throw new NetworkError(`Network error: ${(error as Error).message}`);
  }
  
  if (!response.ok) {
    const errorBody = await response.text();
    throw new ApiError(
      response.status,
      `HTTP error ${response.status}`,
      errorBody
    );
  }
  
  try {
    const data: T = await response.json();
    return data;
  } catch (error) {
    throw new Error(`Invalid JSON response: ${(error as Error).message}`);
  }
}

// Using typed errors
async function loadUser(id: number): Promise<User | null> {
  try {
    const user = await fetchWithErrorHandling<User>(`/api/users/${id}`);
    return user;
  } catch (error) {
    if (error instanceof ApiError) {
      if (error.statusCode === 404) {
        console.log("User not found");
        return null;
      }
      console.error(`API error ${error.statusCode}:`, error.message);
    } else if (error instanceof NetworkError) {
      console.error("Network error:", error.message);
    } else {
      console.error("Unknown error:", error);
    }
    return null;
  }
}

// Result type pattern
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function fetchUserResult(id: number): Promise<Result<User>> {
  try {
    const user = await fetchWithErrorHandling<User>(`/api/users/${id}`);
    return { success: true, data: user };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error(String(error))
    };
  }
}

// Using Result type
async function processUser(id: number) {
  const result = await fetchUserResult(id);
  
  if (result.success) {
    console.log("User:", result.data.name);
    // TypeScript knows result.data exists here
  } else {
    console.error("Error:", result.error.message);
    // TypeScript knows result.error exists here
  }
}

// Retry logic
async function fetchWithRetry<T>(
  url: string,
  maxRetries: number = 3,
  delayMs: number = 1000
): Promise<T> {
  let lastError: Error;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetchWithErrorHandling<T>(url);
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));
      
      if (i < maxRetries - 1) {
        console.log(`Retry ${i + 1}/${maxRetries - 1} after ${delayMs}ms`);
        await delay(delayMs);
      }
    }
  }
  
  throw lastError!;
}

// Helper delay function
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

interface User {
  id: number;
  name: string;
  email: string;
}

Practical Examples

Example 1: Data Loader with Caching

data-loader.ts
interface CacheEntry<T> {
  data: T;
  timestamp: number;
  expiresIn: number;
}

class DataLoader<T> {
  private cache = new Map<string, CacheEntry<T>>();
  
  constructor(private defaultTTL: number = 60000) {}
  
  async load(
    key: string,
    fetcher: () => Promise<T>,
    ttl: number = this.defaultTTL
  ): Promise<T> {
    // Check cache
    const cached = this.cache.get(key);
    
    if (cached) {
      const age = Date.now() - cached.timestamp;
      
      if (age < cached.expiresIn) {
        console.log(`Cache hit for ${key}`);
        return cached.data;
      }
      
      console.log(`Cache expired for ${key}`);
    }
    
    // Fetch fresh data
    console.log(`Fetching fresh data for ${key}`);
    const data = await fetcher();
    
    // Update cache
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      expiresIn: ttl
    });
    
    return data;
  }
  
  invalidate(key: string): void {
    this.cache.delete(key);
  }
  
  clear(): void {
    this.cache.clear();
  }
}

// Usage
interface User {
  id: number;
  name: string;
  email: string;
}

interface Product {
  id: number;
  name: string;
  price: number;
}

const userLoader = new DataLoader<User>();
const productLoader = new DataLoader<Product>();

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

async function fetchProduct(id: number): Promise<Product> {
  const response = await fetch(`/api/products/${id}`);
  return response.json();
}

async function loadUserData() {
  // First call - fetches from API
  const user1 = await userLoader.load(
    'user:1',
    () => fetchUser(1),
    30000  // 30 second TTL
  );
  console.log(user1);
  
  // Second call - returns from cache
  const user2 = await userLoader.load(
    'user:1',
    () => fetchUser(1)
  );
  console.log(user2);
  
  // Different user - fetches from API
  const user3 = await userLoader.load(
    'user:2',
    () => fetchUser(2)
  );
  console.log(user3);
}

loadUserData();

Example 2: Batch Request Handler

batch-handler.ts
interface BatchRequest<T> {
  id: string;
  promise: Promise<T>;
  resolve: (value: T) => void;
  reject: (error: Error) => void;
}

class BatchRequestHandler<TInput, TOutput> {
  private queue: Map<string, BatchRequest<TOutput>> = new Map();
  private batchTimeout: NodeJS.Timeout | null = null;
  
  constructor(
    private batchFn: (inputs: TInput[]) => Promise<TOutput[]>,
    private delayMs: number = 10
  ) {}
  
  async request(id: string, input: TInput): Promise<TOutput> {
    // Check if already in queue
    const existing = this.queue.get(id);
    if (existing) {
      return existing.promise;
    }
    
    // Create new promise
    let resolve: (value: TOutput) => void;
    let reject: (error: Error) => void;
    
    const promise = new Promise<TOutput>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    
    // Add to queue
    this.queue.set(id, {
      id,
      promise,
      resolve: resolve!,
      reject: reject!
    });
    
    // Schedule batch execution
    this.scheduleBatch();
    
    return promise;
  }
  
  private scheduleBatch(): void {
    if (this.batchTimeout) {
      return;  // Already scheduled
    }
    
    this.batchTimeout = setTimeout(() => {
      this.executeBatch();
    }, this.delayMs);
  }
  
  private async executeBatch(): Promise<void> {
    this.batchTimeout = null;
    
    const batch = Array.from(this.queue.values());
    
    if (batch.length === 0) {
      return;
    }
    
    console.log(`Executing batch of ${batch.length} requests`);
    
    // Clear queue
    this.queue.clear();
    
    try {
      // Execute batch
      const inputs = batch.map(req => ({ id: req.id })) as TInput[];
      const results = await this.batchFn(inputs);
      
      // Resolve individual promises
      batch.forEach((req, index) => {
        req.resolve(results[index]);
      });
    } catch (error) {
      // Reject all promises
      batch.forEach(req => {
        req.reject(error as Error);
      });
    }
  }
}

// Usage
interface UserInput {
  id: string;
}

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUsersBatch(inputs: UserInput[]): Promise<User[]> {
  const ids = inputs.map(input => input.id).join(',');
  console.log(`Fetching users: ${ids}`);
  
  const response = await fetch(`/api/users?ids=${ids}`);
  const users: User[] = await response.json();
  return users;
}

const userBatcher = new BatchRequestHandler<UserInput, User>(
  fetchUsersBatch,
  50  // 50ms batch window
);

async function loadMultipleUsers() {
  // These will be batched together
  const [user1, user2, user3] = await Promise.all([
    userBatcher.request('1', { id: '1' }),
    userBatcher.request('2', { id: '2' }),
    userBatcher.request('3', { id: '3' })
  ]);
  
  console.log(user1, user2, user3);
  // Only one API call made for all three users!
}

loadMultipleUsers();

Example 3: Sequential and Parallel Processing

processing-patterns.ts
interface Task {
  id: number;
  name: string;
  duration: number;
}

// Sequential processing - one at a time
async function processSequential(tasks: Task[]): Promise<void> {
  console.log("Starting sequential processing...");
  const startTime = Date.now();
  
  for (const task of tasks) {
    console.log(`Processing ${task.name}...`);
    await delay(task.duration);
    console.log(`Completed ${task.name}`);
  }
  
  const totalTime = Date.now() - startTime;
  console.log(`Sequential total time: ${totalTime}ms`);
}

// Parallel processing - all at once
async function processParallel(tasks: Task[]): Promise<void> {
  console.log("Starting parallel processing...");
  const startTime = Date.now();
  
  await Promise.all(
    tasks.map(async (task) => {
      console.log(`Processing ${task.name}...`);
      await delay(task.duration);
      console.log(`Completed ${task.name}`);
    })
  );
  
  const totalTime = Date.now() - startTime;
  console.log(`Parallel total time: ${totalTime}ms`);
}

// Concurrent with limit - N at a time
async function processConcurrent(
  tasks: Task[],
  concurrency: number
): Promise<void> {
  console.log(`Starting concurrent processing (max ${concurrency})...`);
  const startTime = Date.now();
  
  const results: Promise<void>[] = [];
  const executing: Promise<void>[] = [];
  
  for (const task of tasks) {
    const promise = (async () => {
      console.log(`Processing ${task.name}...`);
      await delay(task.duration);
      console.log(`Completed ${task.name}`);
    })();
    
    results.push(promise);
    
    if (concurrency <= tasks.length) {
      const e = promise.then(() => {
        executing.splice(executing.indexOf(e), 1);
      });
      executing.push(e);
      
      if (executing.length >= concurrency) {
        await Promise.race(executing);
      }
    }
  }
  
  await Promise.all(results);
  
  const totalTime = Date.now() - startTime;
  console.log(`Concurrent total time: ${totalTime}ms`);
}

// Usage
const tasks: Task[] = [
  { id: 1, name: "Task 1", duration: 1000 },
  { id: 2, name: "Task 2", duration: 2000 },
  { id: 3, name: "Task 3", duration: 1500 },
  { id: 4, name: "Task 4", duration: 1000 },
  { id: 5, name: "Task 5", duration: 500 }
];

async function compareProcessing() {
  await processSequential(tasks);
  // Total: ~6000ms (sum of all)
  
  console.log("\n---\n");
  
  await processParallel(tasks);
  // Total: ~2000ms (max of all)
  
  console.log("\n---\n");
  
  await processConcurrent(tasks, 2);
  // Total: ~3500ms (limited concurrency)
}

function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

compareProcessing();

Test Your Knowledge

Type Safety Check

Which async/await pattern is correctly typed?

// Option A
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const user: User = await response.json();
  return user;
}

// Option B
async function getData() {
  const data = await fetch('/api/data');
  return data.json();  // What's the return type?
}

// Option C
function loadData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data loaded");
    }, 1000);
  });
}

// Option D
async function getUsers(): User[] {
  const response = await fetch('/api/users');
  return response.json();
}

Common TypeScript Error

Code with Error
// Missing Promise wrapper in return type
async function fetchData(): string {  // ❌ Error!
  const response = await fetch('/api/data');
  const data = await response.json();
  return data;
}

// Wrong: async function returns string
// Right: async function returns Promise<string>

// Another error: not handling rejection
async function getUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const user = await response.json();
  // No error handling - will crash on network error!
  return user;
}

const user = await getUser(1);

❌ The return type of an async function must be the global Promise<T> type. Did you mean to write Promise<string>?

What's Wrong?

Async functions always return a Promise, even if you just return a value. TypeScript requires you to specify Promise<T> as the return type. Also, always handle potential errors in async code.

Corrected Code
// ✅ Correct return type with Promise
async function fetchData(): Promise<string> {
  const response = await fetch('/api/data');
  const data = await response.json();
  return data;
}

// ✅ With error handling
async function getUser(id: number): Promise<User> {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const user: User = await response.json();
    return user;
  } catch (error) {
    console.error("Failed to fetch user:", error);
    throw error;  // Re-throw or handle appropriately
  }
}

// ✅ Using the function with error handling
async function loadUserData() {
  try {
    const user = await getUser(1);
    console.log("User loaded:", user);
  } catch (error) {
    console.error("Error loading user:", error);
  }
}

// ✅ With type guard for error
async function safeFetchUser(id: number): Promise<User | null> {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      return null;  // Return null instead of throwing
    }
    
    const user: User = await response.json();
    return user;
  } catch (error) {
    console.error("Error:", (error as Error).message);
    return null;
  }
}

Solution: Add Promise wrapper to return type and handle errors

Best Practices

  1. Always type Promise return types explicitly - Promise<T>
  2. Use try/catch for error handling in async functions
  3. Handle both success and error cases - don't assume success
  4. Type error objects with as Error or custom error types
  5. Use Promise.all for parallel operations - faster than sequential
  6. Add timeouts for network requests - prevent hanging
  7. Clean up pending promises - avoid memory leaks
  8. Consider retry logic for failed network requests
best-practices-example.ts
// ✅ Good: Explicit Promise type
async function fetchData(): Promise<string> {
  const response = await fetch('/api/data');
  return response.text();
}

// ✅ Good: Error handling
async function safeLoad(): Promise<User | null> {
  try {
    const user = await fetchUser(1);
    return user;
  } catch (error) {
    console.error("Error:", (error as Error).message);
    return null;
  }
}

// ✅ Good: Type errors properly
async function loadWithTypedError() {
  try {
    await riskyOperation();
  } catch (error) {
    if (error instanceof ApiError) {
      console.log("API error:", error.statusCode);
    } else if (error instanceof Error) {
      console.log("Error:", error.message);
    }
  }
}

// ✅ Good: Parallel operations
async function loadAll() {
  const [users, products] = await Promise.all([
    fetchUsers(),
    fetchProducts()
  ]);
  return { users, products };
}

// ❌ Avoid: Missing Promise wrapper
async function bad(): string {  // Should be Promise<string>
  return "data";
}

// ❌ Avoid: Unhandled errors
async function alsoBad() {
  const data = await fetch('/api/data');
  // No error handling - will crash on failure!
  return data.json();
}

// ❌ Avoid: Sequential when parallel possible
async function slow() {
  const users = await fetchUsers();     // Wait
  const products = await fetchProducts();  // Then wait again
  // Better: await Promise.all([...])
}

interface User { id: number; name: string; }
async function fetchUser(id: number): Promise<User> {
  return { id, name: "User" };
}
async function fetchUsers(): Promise<User[]> { return []; }
async function fetchProducts(): Promise<any[]> { return []; }
async function riskyOperation(): Promise<void> {}
class ApiError extends Error { statusCode = 500; }

Key Takeaways

  • Async functions always return Promise<T>
  • Type the resolved value with generic parameter
  • Use await to unwrap Promise values
  • Always handle errors with try/catch in async code
  • Promise.all runs promises in parallel
  • Promise.race returns first completed promise
  • Promise.allSettled waits for all, regardless of success/failure
  • Type error objects explicitly in catch blocks
  • Fetch API returns Promise<Response>
  • Generic functions work great with Promises

What's Next?

Excellent work! You've mastered asynchronous TypeScript with Promises and async/await. Next, we'll explore Working with APIs and JSON—how to type API responses, handle JSON data, create API clients, and work with REST APIs in TypeScript. This builds directly on async/await patterns!

Get ready to master API integration with TypeScript!

Mastering async TypeScript with Promises! Check this out!

Previous
TypeScript with React
Next
Working with APIs and JSON

Never Miss a New TypeScript Tutorial

Join 1,000+ developers learning TypeScript step-by-step. Get new tutorials, type safety tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

TypeScript Tutorials

0 of 30 completed

Your Progress0%

Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo