Back to patterns
Promise Chaining
WORKFLOW
Visualize and debug complex asynchronous flows and promise chains for structured reasoning and step-by-step task completion.
Use Cases
- Complex data fetching workflows
- Multi-step form submissions
- Dependent API calls
- Resource cleanup chains
Production Implementation
Implementation
// Real-world example: User checkout flow with inventory check, payment, and order creation
interface InventoryCheck {
productId: string;
available: boolean;
quantity: number;
}
interface PaymentResult {
transactionId: string;
status: 'success' | 'failed';
amount: number;
}
interface Order {
orderId: string;
userId: string;
items: Array<{ productId: string; quantity: number }>;
status: 'pending' | 'confirmed' | 'failed';
}
class CheckoutService {
private logger = console; // Replace with your logging service
async processCheckout(
userId: string,
items: Array<{ productId: string; quantity: number }>
): Promise<Order> {
try {
// Step 1: Check inventory for all items
const inventoryChecks = await this.checkInventory(items);
const unavailableItems = inventoryChecks.filter(check => !check.available);
if (unavailableItems.length > 0) {
throw new Error(`Items out of stock: ${unavailableItems.map(item => item.productId).join(', ')}`);
}
// Step 2: Calculate total and process payment
const total = await this.calculateTotal(items);
const paymentResult = await this.processPayment(userId, total);
if (paymentResult.status === 'failed') {
throw new Error(`Payment failed for transaction ${paymentResult.transactionId}`);
}
// Step 3: Create order
const order = await this.createOrder(userId, items, paymentResult);
// Step 4: Update inventory (should be transactional in production)
await this.updateInventory(items);
this.logger.info('Checkout completed successfully', {
orderId: order.orderId,
userId,
items: items.length,
total: paymentResult.amount
});
return order;
} catch (error) {
this.logger.error('Checkout process failed', {
userId,
items: items.length,
error: error instanceof Error ? error.message : 'Unknown error'
});
// Attempt cleanup/rollback if needed
await this.handleCheckoutError(error, userId, items);
throw error;
}
}
private async checkInventory(
items: Array<{ productId: string; quantity: number }>
): Promise<InventoryCheck[]> {
try {
// Parallel inventory checks for all items
return await Promise.all(
items.map(async item => {
const inventory = await this.inventoryAPI.check(item.productId);
return {
productId: item.productId,
available: inventory.quantity >= item.quantity,
quantity: inventory.quantity
};
})
);
} catch (error) {
this.logger.error('Inventory check failed', { items, error });
throw new Error('Failed to verify inventory availability');
}
}
private async calculateTotal(
items: Array<{ productId: string; quantity: number }>
): Promise<number> {
try {
const prices = await Promise.all(
items.map(item => this.pricingAPI.get(item.productId))
);
return items.reduce((total, item, index) => {
return total + (prices[index] * item.quantity);
}, 0);
} catch (error) {
this.logger.error('Price calculation failed', { items, error });
throw new Error('Failed to calculate order total');
}
}
private async processPayment(
userId: string,
amount: number
): Promise<PaymentResult> {
try {
const paymentResult = await this.paymentAPI.charge({
userId,
amount,
currency: 'USD'
});
this.logger.info('Payment processed', {
userId,
amount,
transactionId: paymentResult.transactionId
});
return paymentResult;
} catch (error) {
this.logger.error('Payment processing failed', {
userId,
amount,
error
});
throw new Error('Payment processing failed');
}
}
private async createOrder(
userId: string,
items: Array<{ productId: string; quantity: number }>,
payment: PaymentResult
): Promise<Order> {
try {
const order = await this.orderAPI.create({
userId,
items,
paymentId: payment.transactionId,
status: 'confirmed'
});
this.logger.info('Order created', {
orderId: order.orderId,
userId,
items: items.length
});
return order;
} catch (error) {
this.logger.error('Order creation failed', {
userId,
items,
paymentId: payment.transactionId,
error
});
throw new Error('Failed to create order');
}
}
private async updateInventory(
items: Array<{ productId: string; quantity: number }>
): Promise<void> {
try {
await Promise.all(
items.map(item =>
this.inventoryAPI.decrease(item.productId, item.quantity)
)
);
} catch (error) {
this.logger.error('Inventory update failed', { items, error });
throw new Error('Failed to update inventory');
}
}
private async handleCheckoutError(
error: unknown,
userId: string,
items: Array<{ productId: string; quantity: number }>
): Promise<void> {
this.logger.warn('Initiating checkout error cleanup', {
userId,
items: items.length,
error
});
try {
// Implement your cleanup/rollback logic here
// For example:
// - Refund payment if it was processed
// - Restore inventory if it was decreased
// - Update order status if it was created
} catch (cleanupError) {
this.logger.error('Cleanup after checkout failure failed', {
originalError: error,
cleanupError
});
}
}
}
// Usage Example
async function handleUserCheckout(userId: string, cartItems: CartItem[]) {
const checkoutService = new CheckoutService();
try {
const order = await checkoutService.processCheckout(userId, cartItems);
// Handle successful checkout
notifyUser(userId, {
type: 'checkout_success',
orderId: order.orderId
});
} catch (error) {
// Handle checkout failure
notifyUser(userId, {
type: 'checkout_failed',
error: error instanceof Error ? error.message : 'Checkout failed'
});
// Redirect to error page or show error message
throw error;
}
}
Code Examples
Common Promise Chain Issues
typescript
// ❌ Common Issues in Promise Chains
async function processOrder(orderId: string) {
// Issue 1: No error handling
const order = await fetchOrder(orderId);
const user = await fetchUser(order.userId);
await processPayment(order.amount);
// Issue 2: Sequential requests that could be parallel
const items = [];
for (const itemId of order.itemIds) {
const item = await fetchItem(itemId);
items.push(item);
}
// Issue 3: No cleanup on failure
await updateInventory(items);
await sendConfirmation(user.email);
}
Common issues include lack of error handling, inefficient sequential requests, and missing cleanup logic.
Improved Promise Chain
typescript
// ✅ Better Promise Chain Implementation
async function processOrder(orderId: string) {
let inventoryUpdated = false;
try {
// Fetch order and user in parallel
const [order, user] = await Promise.all([
fetchOrder(orderId),
fetchUser(order.userId)
]);
// Validate order status
if (order.status !== 'pending') {
throw new Error(`Invalid order status: ${order.status}`);
}
// Process payment with retry logic
const payment = await retry(
() => processPayment(order.amount),
{ maxAttempts: 3 }
);
// Fetch items in parallel
const items = await Promise.all(
order.itemIds.map(fetchItem)
);
// Update inventory with transaction
await beginTransaction();
await updateInventory(items);
inventoryUpdated = true;
await commitTransaction();
// Send confirmation
await sendConfirmation(user.email);
logger.info('Order processed successfully', {
orderId,
userId: user.id,
amount: order.amount
});
} catch (error) {
logger.error('Order processing failed', {
orderId,
error: error instanceof Error ? error.message : 'Unknown error'
});
// Cleanup: Rollback inventory if needed
if (inventoryUpdated) {
try {
await rollbackInventory(items);
} catch (rollbackError) {
logger.error('Inventory rollback failed', {
orderId,
error: rollbackError
});
}
}
throw error;
}
}
This improved version includes parallel requests, proper error handling, cleanup logic, and logging.
Best Practices
- Use Promise.all() for parallel operations when requests are independent
- Implement proper error handling with specific error types
- Add logging for debugging and monitoring
- Include cleanup/rollback logic for failures
- Use transactions for related operations
- Implement retry logic for transient failures
- Add proper TypeScript types for better maintainability
Common Pitfalls
- Running requests sequentially when they could be parallel
- Missing error handling or using generic catch-all handlers
- Not implementing cleanup logic for partial failures
- Forgetting to log important events and errors
- Not handling edge cases in the business logic
- Missing type definitions leading to runtime errors
Related Patterns
Memory Leaks 101
A systematic approach to identifying and fixing memory leaks through heap snapshot analysis, memory usage visualization, and common leak pattern detection.
API Integration
A comprehensive debugging flow for API integrations, focusing on request/response cycles, error handling patterns, and timeout/retry strategies.