Promises and async await

Promise of T

An async function always returns a Promise<T>, where T is the type of the value you return. await unwraps it back to T.

Example

type Product = { id: number; name: string; price: number };

const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));

async function getProduct(id: number): Promise<Product> {
  await delay(20); // pretend this is a database call
  return { id, name: "Product " + id, price: id * 100 };
}

async function main() {
  console.log("loading...");
  const p = await getProduct(7); // Product, not Promise<Product>
  console.log(p.name, p.price);

  const pending = getProduct(8); // Promise<Product>
  pending.then((x) => console.log("then:", x.name));
}
main();

Output

loading...
Product 7 700
then: Product 8

Forgetting await

Example

Without await you have a Promise, not the price (TS2339: toFixed does not exist on Promise<number>).

async function getPrice(): Promise<number> {
  return 499;
}

async function main() {
  const price = getPrice();
  console.log(price.toFixed(2));
}
main();

Compiler output (tsc)

Line 7: error TS2339: Property 'toFixed' does not exist on type 'Promise<number>'.

Creating a Typed Promise

Example

new Promise<T> types the resolve callback. Here a callback-style function is converted to a promise.

function legacyLookup(pin: string, cb: (err: Error | null, city?: string) => void): void {
  setTimeout(() => (pin === "500081" ? cb(null, "Hyderabad") : cb(new Error("Unknown PIN"))), 10);
}

function lookup(pin: string): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    legacyLookup(pin, (err, city) => (err ? reject(err) : resolve(city ?? "")));
  });
}

async function main() {
  console.log(await lookup("500081"));
  try {
    await lookup("000000");
  } catch (e) {
    console.log("Failed:", (e as Error).message);
  }
}
main();

Output

Hyderabad
Failed: Unknown PIN