GAZAR

Principal Engineer | Mentor

Exploring Promise APIs in TypeScript

Exploring Promise APIs in TypeScript

Promises are a fundamental concept in asynchronous programming, providing a structured way to work with asynchronous operations in JavaScript. In TypeScript, promises can be annotated with specific types to enforce type safety and improve code documentation. This article explores various Promise APIs available in TypeScript, highlighting their usage with examples and demonstrating how they can be leveraged to handle asynchronous tasks effectively.

Promises in TypeScript

Promises represent the eventual completion or failure of an asynchronous operation and allow developers to work with asynchronous code in a more intuitive and structured manner. In TypeScript, promises can be annotated with specific types to enforce type safety and provide better code documentation and readability.

const fetchData = (): Promise<string> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data fetched successfully');
    }, 2000);
  });
};

const fetchDataAsync = async () => {
  const data: string = await fetchData();
  console.log(data);
};

Chaining Promises in TypeScript

Promises can be chained together using the then() method, allowing for sequential execution of asynchronous tasks. TypeScript's type inference capabilities ensure that the resolved value of one promise matches the expected input type of the next promise in the chain.

fetchData()
  .then((data: string) => {
    console.log(data);
    return processData(data);
  })
  .then((result: number) => {
    console.log(`Processed result: ${result}`);
  })
  .catch((error: Error) => {
    console.error('An error occurred:', error.message);
  });

Error Handling with Promises in TypeScript:

With TypeScript, developers can leverage static typing to ensure comprehensive error handling within promise chains. By annotating catch handlers with specific error types, developers can catch and handle errors with precision, enhancing code robustness and reliability.

fetchData()
  .then((data: string) => {
    console.log(data);
    return processData(data);
  })
  .catch((error: Error) => {
    console.error('Error fetching or processing data:', error.message);
    throw error; // Rethrow error for further handling
  });

Combining Promises with async/await:

Async/await syntax in TypeScript provides a more concise and synchronous-looking way to work with promises, making asynchronous code easier to read and maintain. By marking functions as async, developers can use await to pause execution until a promise is resolved or rejected.

const fetchDataAsync = async (): Promise<void> => {
  try {
    const data: string = await fetchData();
    console.log(data);
  } catch (error) {
    console.error('An error occurred:', error.message);
  }
};

Promise.all():

Imagine a scenario where you need to execute multiple asynchronous operations in parallel and await their completion. Enter Promise.all(), a lifesaver in such situations. With Promise.all(), you can pass an array of promises and await all of them to resolve simultaneously.

const promises: Promise<number>[] = [
  asyncOperation1(),
  asyncOperation2(),
  asyncOperation3(),
];

try {
  const results: number[] = await Promise.all(promises);
  console.log('All promises resolved:', results);
} catch (error) {
  console.error('Error occurred:', error);
}

Promise.race():

Now, picture a scenario where you have multiple asynchronous operations, but you only care about the result of the first one to resolve. Enter Promise.race(), the hero of such scenarios. With Promise.race(), you can pass an array of promises and await the result of the first one to resolve.

const promises: Promise<number>[] = [
  asyncOperation1(),
  asyncOperation2(),
  asyncOperation3(),
];

try {
  const result: number = await Promise.race(promises);
  console.log('First promise resolved:', result);
} catch (error) {
  console.error('Error occurred:', error);
}

Promise.allSettled()

The Promise.allSettled() method is a static method available in JavaScript that returns a promise that resolves after all of the provided promises have either resolved or rejected, with an array of objects that each describes the outcome of each promise. Unlike Promise.all(), which rejects immediately upon any of the input promises rejecting, Promise.allSettled() waits for all of the promises to settle (either resolve or reject) before resolving.

const promises = [
  Promise.resolve('Resolved promise'),
  Promise.reject(new Error('Rejected promise')),
  Promise.resolve('Another resolved promise')
];

Promise.allSettled(promises)
  .then((results) => {
    results.forEach((result) => {
      if (result.status === 'fulfilled') {
        console.log('Fulfilled:', result.value);
      } else {
        console.log('Rejected:', result.reason.message);
      }
    });
  })
  .catch((error) => {
    console.error('An error occurred:', error);
  });

Promise.any()

The Promise.any() method is a Promise combinator that takes an iterable of Promises and returns a single Promise that resolves as soon as one of the input Promises fulfills, or rejects if all of the input Promises reject. It's somewhat similar to Promise.race(), but it doesn't immediately reject with the first rejection, it waits for any of the promises to fulfill.

const promises = [
  new Promise((resolve, reject) => setTimeout(() => resolve('First promise resolved'), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('Second promise rejected')), 500)),
  new Promise((resolve, reject) => setTimeout(() => resolve('Third promise resolved'), 1500))
];

Promise.any(promises)
  .then((result) => {
    console.log('First fulfilled promise:', result);
  })
  .catch((error) => {
    console.error('All promises were rejected:', error);
  });

.finally()

The .finally() method is used to specify a function to be executed when a Promise is settled, whether it's fulfilled or rejected. This method is often used for cleanup operations or to perform actions that should occur regardless of the Promise's outcome.

promise
  .then((result) => {
    console.log('Promise fulfilled with result:', result);
  })
  .catch((error) => {
    console.error('Promise rejected with error:', error);
  })
  .finally(() => {
    console.log('Promise settled, performing cleanup...');
  });

In conclusion, Promise APIs in TypeScript offer powerful tools for managing asynchronous operations in a structured and type-safe manner. By understanding and utilizing APIs such as .then(), .catch(), .finally(), Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any(), developers can write cleaner, more maintainable code and handle asynchronous tasks with confidence. Experimenting with these APIs and incorporating them into your TypeScript projects can lead to more robust and efficient asynchronous code.