How to Work with TypeScript and Promises

TypeScript enhances JavaScript development by adding static types. When working with asynchronous code, promises are used to handle operations that complete in the future. TypeScript provides tools to manage promises with type safety and clarity. This guide explores how to work with promises in TypeScript, from basic usage to advanced scenarios.

Understanding Promises

A promise is an object representing the eventual completion or failure of an asynchronous operation. It provides methods to handle the result or error of the operation. In TypeScript, promises can be typed to ensure that they resolve with the expected data type.

Basic Usage of Promises

Creating and using promises in TypeScript is straightforward. Here is an example of a promise that resolves with a string:

function fetchData(): Promise {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched successfully!");
    }, 1000);
  });
}

fetchData().then((data) => {
  console.log(data); // Outputs: Data fetched successfully!
}).catch((error) => {
  console.error("Error:", error);
});

In this example, fetchData returns a promise that resolves with a string. The then method handles the resolved value, while catch handles any errors.

Handling Promises with Async/Await

TypeScript supports the async/await syntax, which provides a more readable way to work with promises. Here’s how to use async/await with promises:

async function fetchDataAsync(): Promise {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched successfully!");
    }, 1000);
  });
}

async function processData() {
  try {
    const data = await fetchDataAsync();
    console.log(data); // Outputs: Data fetched successfully!
  } catch (error) {
    console.error("Error:", error);
  }
}

processData();

In this example, the fetchDataAsync function is declared as async, allowing it to use the await keyword to wait for the promise to resolve. Errors are caught using a try/catch block.

Typing Promises with Generics

TypeScript allows specifying the type of data a promise resolves with using generics. This ensures that the types are correctly managed throughout the code. Here is an example of a typed promise:

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

function fetchUser(): Promise {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ id: 1, name: "John Doe" });
    }, 1000);
  });
}

fetchUser().then((user) => {
  console.log(user.id, user.name); // Outputs: 1 John Doe
}).catch((error) => {
  console.error("Error:", error);
});

In this example, fetchUser returns a promise that resolves with a User object. The type is specified using generics, ensuring type safety.

Using Promises in TypeScript with API Calls

Promises are often used with API calls to handle asynchronous data fetching. TypeScript's type system helps manage responses from APIs:

async function getUserData(userId: number): Promise {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  if (!response.ok) {
    throw new Error("Network response was not ok.");
  }
  const data: User = await response.json();
  return data;
}

getUserData(1).then((user) => {
  console.log(user);
}).catch((error) => {
  console.error("Error:", error);
});

This example demonstrates how to use fetch to make an API call and handle the response using promises and async/await. The response is typed as User, ensuring type safety.

Conclusion

Working with promises in TypeScript provides enhanced type safety and clarity for asynchronous operations. By using TypeScript’s typing system, async/await syntax, and generics, developers can manage promises effectively and build more reliable applications.