Promise
Core
Analogy
Imagine this scenario: I walk up to the counter at a fast-food restaurant, and place an order for a cheeseburger. I hand the cashier $1.47. By placing my order and paying for it, I've made a request for a value back (the cheeseburger). I've started a transaction.
But often, the cheeseburger is not immediately available for me. The cashier hands me something in place of my cheeseburger: a receipt with an order number on it. This order number is an IOU ("I owe you") promise that ensures that eventually, I should receive my cheeseburger.
So I hold onto my receipt and order number. I know it represents my future cheeseburger, so I don't need to worry about it anymore -- aside from being hungry!
While I wait, I can do other things, like send a text message to a friend that says, "Hey, can you come join me for lunch? I'm going to eat a cheeseburger."
I am reasoning about my future cheeseburger already, even though I don't have it in my hands yet. My brain is able to do this because it's treating the order number as a placeholder for the cheeseburger. The placeholder essentially makes the value time independent. It's a future value.
Eventually, I hear, "Order 113!" and I gleefully walk back up to the counter with receipt in hand. I hand my receipt to the cashier, and I take my cheeseburger in return.
In other words, once my future value was ready, I exchanged my value-promise for the value itself.
But there's another possible outcome. They call my order number, but when I go to retrieve my cheeseburger, the cashier regretfully informs me, "I'm sorry, but we appear to be all out of cheeseburgers." Setting aside the customer frustration of this scenario for a moment, we can see an important characteristic of future values: they can either indicate a success or failure.
Every time I order a cheeseburger, I know that I'll either get a cheeseburger eventually, or I'll get the sad news of the cheeseburger shortage, and I'll have to figure out something else to eat for lunch.
Note: In code, things are not quite as simple, because metaphorically the order number may never be called, in which case we're left indefinitely in an unresolved state. We'll come back to dealing with that case later. - You-Dont-Know-JS
To rephrase this analogy ending: After you exchange the money for the receipt you could have 3 main scenarios:
- Your number gets called, and you get a cheeseburger.
- Your number gets called, and employee informs you they are out of cheeseburgers.
- You never never gets called, they lost your order ticket.
Fundamentals
Promise Fundamentals in TypeScript
What is a Promise?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It provides a cleaner alternative to callbacks for handling async code.
Promise States
- Pending: Initial state, operation in progress
- Fulfilled: Operation completed successfully with a value
- Rejected: Operation failed with an error
Basic Syntax
const promise: Promise<T> = new Promise((resolve, reject) => {
// Async operation
if (success) {
resolve(value); // Type T
} else {
reject(error);
}
});
Type Annotations
// Promise with string result
const getName: Promise<string> = Promise.resolve("Alice");
// Promise with number result
const getAge: Promise<number> = new Promise((resolve) => {
setTimeout(() => resolve(25), 1000);
});
// Promise that may reject
const riskyOperation: Promise<number> = new Promise((resolve, reject) => {
Math.random() > 0.5 ? resolve(42) : reject(new Error("Failed"));
});
Consuming Promises
Using .then() and .catch()
promise
.then((result: T) => {
console.log(result);
return processedValue; // Can chain
})
.catch((error: Error) => {
console.error(error);
})
.finally(() => {
// Cleanup code
});
Using async/await
async function handlePromise(): Promise<void> {
try {
const result: T = await promise;
console.log(result);
} catch (error) {
console.error(error);
}
}
Utility Methods
// Wait for all promises
const results: [string, number] = await Promise.all([
Promise.resolve("hello"),
Promise.resolve(42)
]);
// Race between promises
const first: string | number = await Promise.race([
fetchFromAPI1(),
fetchFromAPI2()
]);
// Wait for all to settle (success or failure)
const outcomes = await Promise.allSettled([promise1, promise2]);
// First fulfilled promise (ignores rejections)
const firstSuccess = await Promise.any([promise1, promise2]);
Generic Type Inference
TypeScript automatically infers Promise types in many cases:
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json(); // Return type inferred as User
}
// Type of userData is User
const userData = await fetchUser(123);
Common Patterns
// Promisifying callbacks
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Error handling with type guards
async function safeOperation(): Promise<Result | null> {
try {
return await riskyOperation();
} catch (error) {
if (error instanceof NetworkError) {
// Handle specific error
}
return null;
}
}
Patterns
Children