TypeScript Async Programming

Working with asynchronous operations using Promises and async/await

⏱️ What is Async Programming?

Asynchronous programming allows your code to handle time-consuming operations without blocking execution. TypeScript provides strong typing for Promises, async/await, and callbacks, making asynchronous code safer and more predictable.


// Simple async function
async function fetchUser(id: number): Promise<string> {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`User ${id}`);
        }, 1000);
    });
}

// Using await
const user = await fetchUser(1);
console.log(user); // "User 1"
                                    

Output:

User 1

Async Programming Concepts

🎁

Promises

Represent future values

const promise: Promise<number> = 
    Promise.resolve(42);

Async/Await

Write async code like sync code

async function getData() {
    return await fetch('/api');
}
🔄

Promise Chaining

Chain multiple async operations

promise
    .then(x => x * 2)
    .then(x => x + 1);

Parallel Execution

Run multiple promises together

await Promise.all([
    fetch('/api/1'),
    fetch('/api/2')
]);

🔹 Creating Promises

Promises represent values that will be available in the future:

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

// Promise that returns a value
function fetchData(): Promise<string> {
    return new Promise((resolve, reject) => {
        const success = true;
        
        if (success) {
            resolve("Data loaded successfully");
        } else {
            reject("Failed to load data");
        }
    });
}

// Using the promise
fetchData()
    .then(data => console.log(data))
    .catch(error => console.error(error));

Output:

Data loaded successfully

🔹 Async/Await Syntax

Async/await makes asynchronous code look synchronous:

// Define an async function
async function getUserData(userId: number): Promise<object> {
    // Simulate API call
    await delay(1000);
    
    return {
        id: userId,
        name: "John Doe",
        email: "[email protected]"
    };
}

// Call async function
async function displayUser() {
    console.log("Fetching user...");
    
    const user = await getUserData(1);
    console.log("User:", user);
    
    console.log("Done!");
}

displayUser();

Output:

Fetching user...
User: { id: 1, name: "John Doe", email: "[email protected]" }
Done!

🔹 Typing Async Functions

TypeScript provides strong typing for async operations:

// Async function with typed return
async function getNumber(): Promise<number> {
    return 42;
}

// Async function with typed parameters
async function processUser(
    id: number, 
    name: string
): Promise<{ success: boolean; message: string }> {
    await delay(500);
    
    return {
        success: true,
        message: `Processed user ${name} with ID ${id}`
    };
}

// Using the typed async function
const result = await processUser(1, "Alice");
console.log(result.message);

Output:

Processed user Alice with ID 1

🔹 Parallel Execution

Run multiple async operations simultaneously:

// Multiple async operations
async function fetchUser(id: number): Promise<string> {
    await delay(1000);
    return `User ${id}`;
}

async function fetchPosts(userId: number): Promise<string[]> {
    await delay(1000);
    return [`Post 1`, `Post 2`, `Post 3`];
}

// Sequential (slow - 2 seconds total)
async function sequentialFetch() {
    const user = await fetchUser(1);      // Wait 1 second
    const posts = await fetchPosts(1);    // Wait 1 second
    return { user, posts };
}

// Parallel (fast - 1 second total)
async function parallelFetch() {
    const [user, posts] = await Promise.all([
        fetchUser(1),      // Both run
        fetchPosts(1)      // at the same time
    ]);
    return { user, posts };
}

const data = await parallelFetch();
console.log(data);

Output:

{ user: "User 1", posts: ["Post 1", "Post 2", "Post 3"] }

🔹 Error Handling

Handle errors in async code with try/catch:

// Async function that might fail
async function riskyOperation(): Promise<string> {
    const random = Math.random();
    
    if (random > 0.5) {
        return "Success!";
    } else {
        throw new Error("Operation failed");
    }
}

// Handle errors with try/catch
async function safeOperation() {
    try {
        const result = await riskyOperation();
        console.log(result);
    } catch (error) {
        if (error instanceof Error) {
            console.error("Error:", error.message);
        }
    } finally {
        console.log("Operation completed");
    }
}

safeOperation();

Output (varies):

Success!
Operation completed

OR

Error: Operation failed
Operation completed

🔹 Common Patterns

Useful async programming patterns:

Promise.all() - Wait for all

const results = await Promise.all([promise1, promise2, promise3]);

Promise.race() - First to finish

const fastest = await Promise.race([promise1, promise2]);

Promise.allSettled() - All results (success or failure)

const outcomes = await Promise.allSettled([promise1, promise2]);

Promise.any() - First successful

const firstSuccess = await Promise.any([promise1, promise2]);

🧠 Test Your Knowledge

What does an async function always return?