TYPESCRIPT

TypeScript Promise: Syntax, Usage, and Examples

A TypeScript promise lets you write asynchronous code that’s clean, readable, and type-safe. With static typing built in, a TypeScript promise implementation helps you catch errors early and understand what values your code will return—especially when working with APIs, timers, or anything that takes time.

How to Use Promises in TypeScript

At its core, a promise represents the eventual result of an asynchronous operation. It can resolve successfully with a value or reject with an error. TypeScript enhances this by letting you declare what type of value the promise will return.

Here’s a basic example:

const fetchName = (): Promise<string> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Alice");
    }, 1000);
  });
};

In this example, the function fetchName returns a Promise<string>, so you know it will eventually resolve with a string. If you try to resolve it with a number, TypeScript will throw a compile-time error.

Resolving and Rejecting a Promise

A promise has three states:

  • Pending – the operation is still running
  • Fulfilled – the operation succeeded
  • Rejected – the operation failed

You resolve a promise using resolve() and reject it using reject():

const getAge = (): Promise<number> => {
  return new Promise((resolve, reject) => {
    const success = true;
    if (success) {
      resolve(30);
    } else {
      reject("Failed to get age");
    }
  });
};

This helps you handle real-world conditions like API failures or user errors.

When to Use TypeScript Promises

You’ll use TypeScript promise objects whenever you:

  • Fetch data from an API
  • Wait for user input
  • Handle timeouts or delays
  • Perform file system operations (Node.js)
  • Run sequences that depend on asynchronous results

A promise in TypeScript shines when you want to keep your async logic manageable without diving into deeply nested callbacks.

Examples of Using Promise TypeScript Syntax

Chaining with .then() and .catch()

fetchName()
  .then((name) => {
    console.log("Name:", name);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

You chain .then() for success and .catch() for failure. Each step has full type awareness thanks to TypeScript’s promise system.

Using async/await

The async and await keywords let you write promise-based code that looks synchronous:

const printName = async () => {
  try {
    const name = await fetchName();
    console.log("User name is:", name);
  } catch (error) {
    console.error("Something went wrong:", error);
  }
};

Here, TypeScript knows that fetchName() returns a Promise<string>, so name is typed as string.

Returning a Promise from a Function

const getUser = (id: number): Promise<{ id: number; name: string }> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id, name: "Charlie" });
    }, 500);
  });
};

The return type clearly communicates that this promise will resolve with an object that includes id and name.

Learn More About Promises in TypeScript

The TypeScript Promise Return Type

When you return a promise from a function, it's a good habit to annotate the return type:

function delayedGreeting(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Hello!");
    }, 1000);
  });
}

You don’t need to annotate it every time, but doing so improves readability and avoids confusion when refactoring later.

Promise.all with TypeScript

You can run multiple asynchronous tasks in parallel using Promise.all:

const promise1 = Promise.resolve(10);
const promise2 = Promise.resolve("hello");

Promise.all([promise1, promise2]).then(([num, str]) => {
  console.log(num * 2); // 20
  console.log(str.toUpperCase()); // HELLO
});

TypeScript infers the tuple types, so you get proper type safety even when handling multiple results.

Promise.race in TypeScript

If you only care about the first settled promise:

const firstToResolve = Promise.race([
  Promise.resolve("fast"),
  new Promise((resolve) => setTimeout(() => resolve("slow"), 1000)),
]);

firstToResolve.then((value) => console.log("First result:", value));

This is great for implementing timeouts or fallback logic.

Promisifying Callbacks

If you use APIs that still rely on callbacks, you can wrap them in a promise:

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

This helps you modernize legacy code while keeping types consistent.

Using Generics in a Promise

TypeScript lets you specify the type of data a promise returns by passing a generic type:

const getData = <T>(data: T): Promise<T> => {
  return new Promise((resolve) => resolve(data));
};

getData<number>(42).then((num) => console.log(num.toFixed(2)));
getData<string>("TypeScript").then((text) => console.log(text.length));

This makes your functions flexible and type-safe for any kind of data.

Type Guards and Error Handling

When dealing with errors, TypeScript promises don’t automatically know what type of error you’ll get. Use type guards or assertions:

try {
  const result = await getUser(1);
  console.log(result.name);
} catch (e) {
  if (typeof e === "string") {
    console.error("Error:", e);
  } else {
    console.error("Unknown error");
  }
}

You can also define custom error classes and use instanceof for better control.

Common Use Cases for TypeScript Promises

Fetching API Data

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

TypeScript infers the return type, but you should always annotate it to avoid surprises.

Waiting for Timeouts

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

await sleep(1000);
console.log("Waited 1 second");

Great for throttling, retry logic, or animations.

Handling Sequential Tasks

async function runTasks() {
  const a = await Promise.resolve(1);
  const b = await Promise.resolve(a + 1);
  const c = await Promise.resolve(b + 1);
  console.log(c); // 3
}

You reduce callback nesting and improve readability with await.

TypeScript Promises vs. Callbacks

Callbacks used to rule async code, but they quickly become hard to manage as your logic grows. Promises flatten that complexity. A promise in TypeScript gives you:

  • Clear typing of what will be returned
  • Better syntax for chaining and error handling
  • Support for parallel and sequential execution
  • Cleaner, easier-to-follow logic

Most modern TypeScript projects use promises over callbacks—especially when working with frameworks like Angular, React, or Node.js.

Best Practices with TypeScript Promises

  • Always annotate the return type of your async functions.
  • Use try/catch when using await to handle errors.
  • Avoid mixing await and .then() unless you have a good reason.
  • Use Promise.all for parallel execution and Promise.race for timeouts.
  • Create utility wrappers for reusable async logic.
  • Use async/await over raw .then() chains in new code.

A TypeScript promise lets you write asynchronous logic without losing control or clarity. It combines JavaScript's powerful async model with the safety of TypeScript’s type system. Whether you're working with API calls, user events, or delays, promises make your logic easier to understand, test, and extend. .

Learn TypeScript for Free
Start learning now
button icon
To advance beyond this tutorial and learn TypeScript by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH