Mastering Asynchronous Operations in TypeScript

Introduction

Handling asynchronous operations efficiently is crucial in modern web development, especially when dealing with APIs, data fetching, and timed operations. TypeScript, a superset of JavaScript, enhances JavaScript’s capabilities, including its handling of async operations. In this article, we explore various asynchronous patterns in TypeScript, including a custom sleep function, fetching data from APIs, and handling multiple async operations simultaneously.

1. Implementing a Sleep Function

JavaScript and TypeScript do not have a built-in sleep function. However, you can simulate this behavior using Promises combined with the setTimeout function. Here’s how you can implement a simple sleep function in TypeScript:

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

async function demoSleep() {
    console.log('Wait for 3 seconds...');
    await sleep(3000);  // Sleep for 3 seconds
    console.log('Done waiting!');
}

demoSleep();

In this example:

  • The sleep function creates a new Promise that resolves after a specified number of milliseconds.
  • The demoSleep async function demonstrates how to use the sleep function by pausing execution for three seconds before continuing.

2. Fetching Data from an API

Data fetching is a common task in web applications. Here’s an example of how to fetch data from a REST API using async/await syntax in TypeScript:

async function fetchData(url: string): Promise<any> {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Failed to fetch data:", error);
        return null;
    }
}

fetchData('https://api.example.com/data')
    .then(data => console.log(data))
    .catch(error => console.error(error));

3. Handling Multiple Asynchronous Operations

When you need to handle multiple asynchronous operations and wait for all to complete before proceeding, Promise.all() comes in handy:

async function fetchMultipleData(urls: string[]): Promise<any[]> {
    try {
        const promises = urls.map(url => fetch(url).then(r => r.json()));
        const results = await Promise.all(promises);
        return results;
    } catch (error) {
        console.error("An error occurred:", error);
        return [];
    }
}

const urls = [
    'https://api.example.com/data1',
    'https://api.example.com/data2',
    'https://api.example.com/data3'
];

fetchMultipleData(urls)
    .then(results => console.log(results))
    .catch(error => console.error(error));

4. Error Handling in Asynchronous Functions

Proper error handling is vital in asynchronous operations to ensure your application remains robust and user-friendly. TypeScript allows structured error handling using try/catch blocks within async functions:

async function secureFetchData(url: string): Promise<any> {
    try {
        const response = await fetch(url);
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Error fetching data:", error);
        throw error;  // Rethrow or handle as needed
    }
}

Using try/catch ensures that your application can gracefully handle errors, such as network issues or invalid responses.

Conclusion

Asynchronous programming is a powerful part of TypeScript, enabling developers to handle tasks such as API requests, timers, and simultaneously running processes efficiently. By mastering these patterns, you can greatly improve the responsiveness and reliability of your applications. Whether you are implementing a simple delay with a sleep function, fetching data from a remote server, or handling multiple async operations, TypeScript provides the tools necessary to do this with ease and elegance.

Leave a comment