Prerequisites
- Understanding of Promise fundamentals ๐
- Experience with Promise.all and Promise.race โก
- Basic error handling patterns ๐ป
What you'll learn
- Master Promise.allSettled for mixed success/failure scenarios ๐ฏ
- Build resilient batch processing systems ๐๏ธ
- Handle partial failures gracefully ๐
- Create fault-tolerant applications with detailed reporting โจ
๐ฏ Introduction
Welcome to the world of resilient Promise handling! ๐ญ Unlike Promise.all which fails fast, or Promise.race which only cares about the winner, Promise.allSettled()
is like a patient teacher who waits for ALL students to finish their test - even if some fail!
Whether youโre processing multiple API calls ๐, validating user data ๐, or running batch operations ๐ฆ, Promise.allSettled ensures you get complete results without letting a few failures ruin the entire operation.
By the end of this tutorial, youโll be a master of fault-tolerant async programming, able to handle mixed success and failure scenarios with grace and detailed insights. Letโs dive into the world of โno Promise left behindโ! ๐
๐ Understanding Promise.allSettled()
๐ค What is Promise.allSettled()?
Think of Promise.allSettled()
as a comprehensive survey ๐. Instead of stopping when the first person says โnoโ (like Promise.all) or celebrating the first โyesโ (like Promise.race), it waits patiently for EVERYONE to respond, then gives you a detailed breakdown of all responses.
// ๐ญ Basic Promise.allSettled example
const promises = [
Promise.resolve("Success! ๐"),
Promise.reject(new Error("Failed! ๐ฅ")),
Promise.resolve("Another success! โจ")
];
const results = await Promise.allSettled(promises);
console.log(results);
/*
[
{ status: 'fulfilled', value: 'Success! ๐' },
{ status: 'rejected', reason: Error: Failed! ๐ฅ },
{ status: 'fulfilled', value: 'Another success! โจ' }
]
*/
๐ก Key Characteristics
- ๐ก๏ธ Never Rejects: Always resolves, even if all promises fail
- ๐ Complete Results: Returns both successes and failures
- ๐ฏ Type Safety: TypeScript provides excellent inference for result types
- โณ Waits for All: Doesnโt return until every promise settles
// ๐จ TypeScript's type inference with Promise.allSettled
const mixed = Promise.allSettled([
Promise.resolve("string"), // PromiseSettledResult<string>
Promise.resolve(42), // PromiseSettledResult<number>
Promise.reject(new Error("boom")) // PromiseSettledResult<never>
]);
// Type: Promise<PromiseSettledResult<string | number>[]>
๐ Comparison with Other Promise Methods
// ๐ Promise.all - Fast failure (one fails, all fail)
try {
const allResults = await Promise.all([goodPromise, badPromise, goodPromise]);
console.log("All succeeded:", allResults); // Won't reach here if badPromise fails
} catch (error) {
console.log("One failed, stopping everything! ๐ฅ"); // Reaches here immediately
}
// ๐ Promise.race - First one wins (ignore the rest)
const raceWinner = await Promise.race([slowPromise, fastPromise, mediumPromise]);
console.log("First to finish:", raceWinner); // Only one result
// ๐ญ Promise.allSettled - Everyone gets heard
const settledResults = await Promise.allSettled([goodPromise, badPromise, goodPromise]);
console.log("All responses received:", settledResults); // Always succeeds with complete info
๐ง Basic Syntax and Usage
๐ Working with PromiseSettledResult
The magic is in the PromiseSettledResult
type:
// ๐ฏ Understanding the result structure
interface PromiseFulfilledResult<T> {
status: 'fulfilled';
value: T;
}
interface PromiseRejectedResult {
status: 'rejected';
reason: any;
}
type PromiseSettledResult<T> = PromiseFulfilledResult<T> | PromiseRejectedResult;
// ๐ ๏ธ Processing results safely
async function processSettledResults() {
const promises = [
Promise.resolve({ id: 1, name: "Alice ๐ฉ" }),
Promise.reject(new Error("Network timeout ๐ก")),
Promise.resolve({ id: 2, name: "Bob ๐จ" }),
Promise.reject(new Error("Invalid data ๐"))
];
const results = await Promise.allSettled(promises);
// ๐จ Process each result based on its status
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`โ
Promise ${index + 1} succeeded:`, result.value);
} else {
console.log(`โ Promise ${index + 1} failed:`, result.reason.message);
}
});
// ๐ Get summary statistics
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`๐ Summary: ${successful.length} succeeded, ${failed.length} failed`);
}
๐ฏ Helper Functions for Processing Results
// ๐ ๏ธ Utility functions for common patterns
class SettledResultsHelper {
// โ
Extract only successful results
static getSuccessful<T>(results: PromiseSettledResult<T>[]): T[] {
return results
.filter((result): result is PromiseFulfilledResult<T> =>
result.status === 'fulfilled'
)
.map(result => result.value);
}
// โ Extract only failed results
static getFailed<T>(results: PromiseSettledResult<T>[]): any[] {
return results
.filter((result): result is PromiseRejectedResult =>
result.status === 'rejected'
)
.map(result => result.reason);
}
// ๐ Get summary statistics
static getSummary<T>(results: PromiseSettledResult<T>[]) {
const successful = this.getSuccessful(results);
const failed = this.getFailed(results);
return {
total: results.length,
successful: successful.length,
failed: failed.length,
successRate: (successful.length / results.length) * 100,
successfulResults: successful,
failedReasons: failed
};
}
// ๐ฏ Partition results into success/failure groups
static partition<T>(results: PromiseSettledResult<T>[]) {
const successes: T[] = [];
const failures: any[] = [];
results.forEach(result => {
if (result.status === 'fulfilled') {
successes.push(result.value);
} else {
failures.push(result.reason);
}
});
return { successes, failures };
}
}
// ๐ฎ Using the helper functions
async function demonstrateHelpers() {
const promises = [
Promise.resolve("Data A ๐"),
Promise.reject(new Error("Server down ๐ฅ")),
Promise.resolve("Data B ๐"),
Promise.reject(new Error("Timeout โฐ")),
Promise.resolve("Data C ๐")
];
const results = await Promise.allSettled(promises);
const summary = SettledResultsHelper.getSummary(results);
console.log("๐ Processing Summary:");
console.log(` โ
Successful: ${summary.successful}/${summary.total} (${summary.successRate.toFixed(1)}%)`);
console.log(` ๐ฆ Results: ${summary.successfulResults.join(', ')}`);
console.log(` โ Failures: ${summary.failedReasons.map(e => e.message).join(', ')}`);
}
๐ก Practical Examples
๐ Example 1: E-commerce Product Validation System
Letโs build a robust product validation system that checks multiple criteria:
// ๐ช Product interfaces
interface Product {
id: string;
name: string;
price: number;
category: string;
emoji: string;
}
interface ValidationResult {
field: string;
isValid: boolean;
message: string;
emoji: string;
}
interface ProductValidationReport {
product: Product;
validations: ValidationResult[];
isOverallValid: boolean;
validationSummary: string;
}
class ProductValidator {
// ๐ Validate product name
private async validateName(product: Product): Promise<ValidationResult> {
// ๐ Simulate API call to check name uniqueness
await this.delay(Math.random() * 300 + 100);
const isValid = product.name.length >= 3 && product.name.length <= 50;
if (!isValid) {
throw new Error("Product name must be 3-50 characters long");
}
return {
field: "name",
isValid: true,
message: `Name "${product.name}" is valid and unique โจ`,
emoji: "โ
"
};
}
// ๐ฐ Validate product price
private async validatePrice(product: Product): Promise<ValidationResult> {
await this.delay(Math.random() * 200 + 50);
const isValid = product.price > 0 && product.price < 10000;
if (!isValid) {
throw new Error("Price must be between $0.01 and $9,999.99");
}
return {
field: "price",
isValid: true,
message: `Price $${product.price.toFixed(2)} is within acceptable range ๐ฐ`,
emoji: "โ
"
};
}
// ๐ท๏ธ Validate product category
private async validateCategory(product: Product): Promise<ValidationResult> {
await this.delay(Math.random() * 400 + 150);
const validCategories = ["electronics", "clothing", "books", "home", "sports"];
const isValid = validCategories.includes(product.category.toLowerCase());
if (!isValid) {
throw new Error(`Category "${product.category}" is not supported. Valid: ${validCategories.join(', ')}`);
}
return {
field: "category",
isValid: true,
message: `Category "${product.category}" is supported ๐ท๏ธ`,
emoji: "โ
"
};
}
// ๐ฆ Validate inventory availability
private async validateInventory(product: Product): Promise<ValidationResult> {
await this.delay(Math.random() * 500 + 200);
// ๐ฒ Simulate 80% success rate for inventory check
const isAvailable = Math.random() > 0.2;
if (!isAvailable) {
throw new Error("Product is currently out of stock");
}
return {
field: "inventory",
isValid: true,
message: `Product is in stock and ready to ship ๐ฆ`,
emoji: "โ
"
};
}
// ๐ก๏ธ Validate compliance (safety, regulations, etc.)
private async validateCompliance(product: Product): Promise<ValidationResult> {
await this.delay(Math.random() * 600 + 300);
// ๐ฒ Simulate 85% success rate for compliance
const isCompliant = Math.random() > 0.15;
if (!isCompliant) {
throw new Error("Product does not meet regulatory compliance standards");
}
return {
field: "compliance",
isValid: true,
message: `Product meets all regulatory requirements ๐ก๏ธ`,
emoji: "โ
"
};
}
// ๐ฏ Comprehensive product validation
async validateProduct(product: Product): Promise<ProductValidationReport> {
console.log(`๐ Starting validation for ${product.emoji} ${product.name}...`);
const validationPromises = [
this.validateName(product),
this.validatePrice(product),
this.validateCategory(product),
this.validateInventory(product),
this.validateCompliance(product)
];
// ๐ญ Use allSettled to get all validation results
const settledResults = await Promise.allSettled(validationPromises);
// ๐ Process results
const validations: ValidationResult[] = [];
let passedCount = 0;
settledResults.forEach((result, index) => {
const fieldNames = ["name", "price", "category", "inventory", "compliance"];
const fieldName = fieldNames[index];
if (result.status === 'fulfilled') {
validations.push(result.value);
passedCount++;
} else {
validations.push({
field: fieldName,
isValid: false,
message: result.reason.message,
emoji: "โ"
});
}
});
const isOverallValid = passedCount === validationPromises.length;
const validationSummary = `${passedCount}/${validationPromises.length} validations passed`;
console.log(`๐ Validation complete: ${validationSummary}`);
return {
product,
validations,
isOverallValid,
validationSummary
};
}
// ๐ Batch validate multiple products
async validateProducts(products: Product[]): Promise<ProductValidationReport[]> {
console.log(`๐ Starting batch validation for ${products.length} products...\n`);
const validationPromises = products.map(product => this.validateProduct(product));
// ๐ญ All products get validated regardless of individual failures
const results = await Promise.allSettled(validationPromises);
const reports: ProductValidationReport[] = [];
let successfulValidations = 0;
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
reports.push(result.value);
if (result.value.isOverallValid) {
successfulValidations++;
}
} else {
// ๐จ Validation process itself failed (shouldn't happen in this example)
console.error(`๐ฅ Validation failed for product ${index + 1}:`, result.reason);
}
});
console.log(`\n๐ Batch validation summary:`);
console.log(` ๐ฆ Products processed: ${products.length}`);
console.log(` โ
Fully valid products: ${successfulValidations}`);
console.log(` โ ๏ธ Products with issues: ${products.length - successfulValidations}`);
return reports;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Demo the product validation system
async function demonstrateProductValidation() {
const validator = new ProductValidator();
const products: Product[] = [
{ id: "1", name: "TypeScript Guide", price: 29.99, category: "books", emoji: "๐" },
{ id: "2", name: "๐โโ๏ธ", price: -5.00, category: "invalid", emoji: "๐" }, // Invalid: bad name & price
{ id: "3", name: "Wireless Headphones", price: 199.99, category: "electronics", emoji: "๐ง" },
{ id: "4", name: "Coffee Mug", price: 15.99, category: "home", emoji: "โ" },
{ id: "5", name: "Gaming Laptop Premium Ultra", price: 2499.99, category: "electronics", emoji: "๐ป" }
];
try {
const reports = await validator.validateProducts(products);
console.log(`\n๐ Detailed Validation Reports:`);
reports.forEach((report, index) => {
console.log(`\n${index + 1}. ${report.product.emoji} ${report.product.name}`);
console.log(` Overall: ${report.isOverallValid ? 'โ
VALID' : 'โ INVALID'} (${report.validationSummary})`);
report.validations.forEach(validation => {
console.log(` ${validation.emoji} ${validation.field}: ${validation.message}`);
});
});
} catch (error) {
console.error("๐ฅ Batch validation failed:", error);
}
}
๐ Example 2: Multi-Service Health Dashboard
Letโs create a comprehensive health monitoring system:
// ๐ฅ Service monitoring interfaces
interface ServiceConfig {
name: string;
url: string;
timeout: number;
critical: boolean;
emoji: string;
}
interface HealthCheckResult {
service: ServiceConfig;
status: 'healthy' | 'degraded' | 'down';
responseTime: number;
details: string;
timestamp: Date;
error?: string;
}
interface SystemHealthReport {
timestamp: Date;
overallStatus: 'healthy' | 'degraded' | 'critical';
services: HealthCheckResult[];
summary: {
total: number;
healthy: number;
degraded: number;
down: number;
criticalIssues: number;
};
}
class HealthMonitor {
private services: ServiceConfig[] = [
{ name: "User API", url: "https://api.example.com/users", timeout: 2000, critical: true, emoji: "๐ฅ" },
{ name: "Payment Gateway", url: "https://payments.example.com/health", timeout: 3000, critical: true, emoji: "๐ณ" },
{ name: "Product Catalog", url: "https://catalog.example.com/status", timeout: 1500, critical: false, emoji: "๐ฆ" },
{ name: "Analytics Service", url: "https://analytics.example.com/ping", timeout: 5000, critical: false, emoji: "๐" },
{ name: "Email Service", url: "https://email.example.com/status", timeout: 4000, critical: false, emoji: "๐ง" },
{ name: "File Storage", url: "https://files.example.com/health", timeout: 2500, critical: true, emoji: "๐๏ธ" }
];
// ๐ฅ Check single service health
private async checkServiceHealth(service: ServiceConfig): Promise<HealthCheckResult> {
const startTime = Date.now();
const timestamp = new Date();
try {
// ๐ Simulate health check with realistic scenarios
await new Promise((resolve, reject) => {
const simulatedResponseTime = Math.random() * service.timeout;
setTimeout(() => {
// ๐ฒ Different failure scenarios based on service type
const rand = Math.random();
if (service.critical && rand < 0.05) {
// 5% chance critical services fail
reject(new Error("Critical service unavailable"));
} else if (!service.critical && rand < 0.15) {
// 15% chance non-critical services fail
reject(new Error("Service temporarily unavailable"));
} else if (rand < 0.25) {
// 25% chance of degraded performance
setTimeout(() => resolve(undefined), simulatedResponseTime * 1.5);
return;
} else {
resolve(undefined);
}
}, simulatedResponseTime);
});
const responseTime = Date.now() - startTime;
// ๐ฏ Determine status based on response time
let status: 'healthy' | 'degraded' | 'down';
let details: string;
if (responseTime < service.timeout * 0.5) {
status = 'healthy';
details = `Response time: ${responseTime}ms (excellent)`;
} else if (responseTime < service.timeout * 0.8) {
status = 'healthy';
details = `Response time: ${responseTime}ms (good)`;
} else {
status = 'degraded';
details = `Response time: ${responseTime}ms (slow but functional)`;
}
return {
service,
status,
responseTime,
details,
timestamp
};
} catch (error) {
const responseTime = Date.now() - startTime;
return {
service,
status: 'down',
responseTime,
details: `Service check failed after ${responseTime}ms`,
timestamp,
error: error.message
};
}
}
// ๐ Comprehensive system health check
async getSystemHealth(): Promise<SystemHealthReport> {
console.log("๐ Performing system-wide health check...");
const timestamp = new Date();
// ๐ญ Check all services simultaneously with allSettled
const healthPromises = this.services.map(service => this.checkServiceHealth(service));
const settledResults = await Promise.allSettled(healthPromises);
// ๐ Process all results (both successful and failed checks)
const services: HealthCheckResult[] = [];
settledResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
services.push(result.value);
} else {
// ๐จ Health check itself failed (shouldn't happen in normal operation)
const service = this.services[index];
services.push({
service,
status: 'down',
responseTime: 0,
details: 'Health check failed to execute',
timestamp,
error: result.reason.message
});
}
});
// ๐ Calculate summary statistics
const summary = {
total: services.length,
healthy: services.filter(s => s.status === 'healthy').length,
degraded: services.filter(s => s.status === 'degraded').length,
down: services.filter(s => s.status === 'down').length,
criticalIssues: services.filter(s => s.service.critical && s.status === 'down').length
};
// ๐ฏ Determine overall system status
let overallStatus: 'healthy' | 'degraded' | 'critical';
if (summary.criticalIssues > 0) {
overallStatus = 'critical';
} else if (summary.down > 0 || summary.degraded > 2) {
overallStatus = 'degraded';
} else if (summary.degraded > 0) {
overallStatus = 'degraded';
} else {
overallStatus = 'healthy';
}
return {
timestamp,
overallStatus,
services,
summary
};
}
// ๐ Display detailed health report
displayHealthReport(report: SystemHealthReport): void {
const statusEmoji = {
healthy: "๐",
degraded: "๐",
critical: "๐ด"
};
console.log(`\n${statusEmoji[report.overallStatus]} System Health Report - ${report.timestamp.toLocaleString()}`);
console.log(`Overall Status: ${report.overallStatus.toUpperCase()}\n`);
console.log("๐ Service Details:");
report.services.forEach(service => {
const emoji = service.status === 'healthy' ? '๐' :
service.status === 'degraded' ? '๐' : '๐ด';
const critical = service.service.critical ? ' [CRITICAL]' : '';
console.log(` ${emoji} ${service.service.emoji} ${service.service.name}${critical}`);
console.log(` Status: ${service.status.toUpperCase()}`);
console.log(` Details: ${service.details}`);
if (service.error) {
console.log(` Error: ${service.error}`);
}
});
console.log(`\n๐ Summary:`);
console.log(` ๐ Healthy: ${report.summary.healthy}/${report.summary.total}`);
console.log(` ๐ Degraded: ${report.summary.degraded}/${report.summary.total}`);
console.log(` ๐ด Down: ${report.summary.down}/${report.summary.total}`);
if (report.summary.criticalIssues > 0) {
console.log(` ๐จ Critical Issues: ${report.summary.criticalIssues}`);
}
}
// ๐ Continuous monitoring
async startMonitoring(intervalMs: number = 30000): Promise<void> {
console.log(`๐ Starting continuous health monitoring (every ${intervalMs/1000}s)...\n`);
const monitor = async () => {
try {
const report = await this.getSystemHealth();
this.displayHealthReport(report);
// ๐จ Alert on critical issues
if (report.overallStatus === 'critical') {
console.log("\n๐จ ALERT: Critical system issues detected!");
const criticalServices = report.services.filter(s =>
s.service.critical && s.status === 'down'
);
criticalServices.forEach(service => {
console.log(` ๐ฅ ${service.service.name}: ${service.error || 'Service down'}`);
});
}
console.log("\n" + "=".repeat(80) + "\n");
} catch (error) {
console.error("๐ฅ Health monitoring failed:", error);
}
};
// ๐ฏ Initial check
await monitor();
// โฐ Schedule recurring checks
setInterval(monitor, intervalMs);
}
}
// ๐ฎ Demo the health monitoring system
async function demonstrateHealthMonitoring() {
const monitor = new HealthMonitor();
try {
// ๐ Single health check
const report = await monitor.getSystemHealth();
monitor.displayHealthReport(report);
// ๐ฏ You can uncomment this for continuous monitoring
// await monitor.startMonitoring(10000); // Check every 10 seconds
} catch (error) {
console.error("๐ฅ Health monitoring demo failed:", error);
}
}
๐ Advanced Concepts
๐งโโ๏ธ Retry with Partial Success Tracking
Handle retries while tracking which operations have already succeeded:
// ๐ Advanced retry system with partial success tracking
class SmartRetryProcessor<T> {
async processWithRetries<T>(
operations: (() => Promise<T>)[],
maxRetries: number = 3,
retryDelay: number = 1000
): Promise<{
results: (T | null)[];
successful: T[];
failed: { index: number; error: any; attempts: number }[];
summary: string;
}> {
const results: (T | null)[] = new Array(operations.length).fill(null);
const successful: T[] = [];
const failed: { index: number; error: any; attempts: number }[] = [];
const pendingIndices = new Set(operations.map((_, i) => i));
for (let attempt = 1; attempt <= maxRetries && pendingIndices.size > 0; attempt++) {
console.log(`\n๐ Attempt ${attempt}: Processing ${pendingIndices.size} operations...`);
// ๐ฏ Only retry the operations that haven't succeeded yet
const currentPromises = Array.from(pendingIndices).map(index =>
operations[index]().then(
result => ({ index, status: 'fulfilled' as const, value: result }),
error => ({ index, status: 'rejected' as const, reason: error })
)
);
// ๐ญ Wait for all current attempts to settle
const settledResults = await Promise.allSettled(currentPromises);
// ๐ Process this round of results
settledResults.forEach(settledResult => {
if (settledResult.status === 'fulfilled') {
const { index, status, value, reason } = settledResult.value;
if (status === 'fulfilled') {
// โ
Operation succeeded
results[index] = value;
successful.push(value);
pendingIndices.delete(index);
console.log(` โ
Operation ${index + 1} succeeded on attempt ${attempt}`);
} else {
// โ Operation failed this round
console.log(` โ Operation ${index + 1} failed on attempt ${attempt}: ${reason.message}`);
if (attempt === maxRetries) {
// ๐ซ Final failure
failed.push({ index, error: reason, attempts: attempt });
pendingIndices.delete(index);
}
}
}
});
// โฐ Delay before next retry (if needed)
if (pendingIndices.size > 0 && attempt < maxRetries) {
console.log(`โฐ Waiting ${retryDelay}ms before next retry...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
const summary = `${successful.length} succeeded, ${failed.length} failed after ${maxRetries} attempts`;
console.log(`\n๐ Final summary: ${summary}`);
return { results, successful, failed, summary };
}
}
// ๐ฎ Demo smart retry processor
async function demonstrateSmartRetry() {
const processor = new SmartRetryProcessor();
// ๐ฏ Create operations with different failure rates
const operations = [
() => new Promise<string>((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.3 ? resolve("Operation A succeeded! ๐
ฐ๏ธ") : reject(new Error("A failed"));
}, 200);
}),
() => new Promise<string>((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.7 ? resolve("Operation B succeeded! ๐
ฑ๏ธ") : reject(new Error("B failed"));
}, 300);
}),
() => new Promise<string>((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.1 ? resolve("Operation C succeeded! ๐") : reject(new Error("C failed"));
}, 150);
}),
() => new Promise<string>((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("Operation D succeeded! ๐ค") : reject(new Error("D failed"));
}, 400);
})
];
try {
const result = await processor.processWithRetries(operations, 4, 500);
console.log(`\n๐ Processing complete!`);
console.log(`โ
Successful operations: ${result.successful.length}`);
result.successful.forEach(success => console.log(` - ${success}`));
if (result.failed.length > 0) {
console.log(`โ Failed operations: ${result.failed.length}`);
result.failed.forEach(failure =>
console.log(` - Operation ${failure.index + 1}: ${failure.error.message} (${failure.attempts} attempts)`)
);
}
} catch (error) {
console.error("๐ฅ Smart retry demo failed:", error);
}
}
๐๏ธ Batch Processing with Progress Reporting
Process large batches with detailed progress tracking:
// ๐ Advanced batch processor with progress tracking
class BatchProcessor<T, R> {
async processBatch<T, R>(
items: T[],
processor: (item: T, index: number) => Promise<R>,
options: {
batchSize?: number;
maxConcurrency?: number;
progressCallback?: (progress: BatchProgress<T, R>) => void;
} = {}
): Promise<BatchResult<T, R>> {
const { batchSize = 10, maxConcurrency = 5, progressCallback } = options;
const results: (R | null)[] = new Array(items.length).fill(null);
const errors: { index: number; item: T; error: any }[] = [];
let completed = 0;
console.log(`๐ Starting batch processing: ${items.length} items, batches of ${batchSize}`);
// ๐ฆ Split items into batches
const batches: T[][] = [];
for (let i = 0; i < items.length; i += batchSize) {
batches.push(items.slice(i, i + batchSize));
}
// ๐ Process each batch
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
const batch = batches[batchIndex];
const batchStartIndex = batchIndex * batchSize;
console.log(`\n๐ฆ Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} items)...`);
// ๐ฏ Limit concurrency within the batch
const promises = batch.map((item, itemIndex) => {
const globalIndex = batchStartIndex + itemIndex;
return processor(item, globalIndex).then(
result => ({ index: globalIndex, success: true, result, item }),
error => ({ index: globalIndex, success: false, error, item })
);
});
// ๐ญ Wait for all items in this batch to complete
const batchResults = await Promise.allSettled(promises);
// ๐ Process batch results
batchResults.forEach(settledResult => {
if (settledResult.status === 'fulfilled') {
const { index, success, result, error, item } = settledResult.value;
if (success) {
results[index] = result;
} else {
errors.push({ index, item, error });
}
completed++;
// ๐ Report progress
const progress: BatchProgress<T, R> = {
total: items.length,
completed,
successful: completed - errors.length,
failed: errors.length,
percentage: (completed / items.length) * 100,
currentBatch: batchIndex + 1,
totalBatches: batches.length,
results: results.filter(r => r !== null) as R[],
errors
};
progressCallback?.(progress);
}
});
console.log(` โ
Batch ${batchIndex + 1} complete: ${completed}/${items.length} total items processed`);
// โฐ Small delay between batches to prevent overwhelming
if (batchIndex < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
const successful = results.filter(r => r !== null) as R[];
const summary = `${successful.length} succeeded, ${errors.length} failed`;
console.log(`\n๐ Batch processing complete! ${summary}`);
return {
items,
results,
successful,
errors,
summary,
statistics: {
totalItems: items.length,
successfulItems: successful.length,
failedItems: errors.length,
successRate: (successful.length / items.length) * 100,
totalBatches: batches.length,
avgItemsPerBatch: items.length / batches.length
}
};
}
}
interface BatchProgress<T, R> {
total: number;
completed: number;
successful: number;
failed: number;
percentage: number;
currentBatch: number;
totalBatches: number;
results: R[];
errors: { index: number; item: T; error: any }[];
}
interface BatchResult<T, R> {
items: T[];
results: (R | null)[];
successful: R[];
errors: { index: number; item: T; error: any }[];
summary: string;
statistics: {
totalItems: number;
successfulItems: number;
failedItems: number;
successRate: number;
totalBatches: number;
avgItemsPerBatch: number;
};
}
// ๐ฎ Demo batch processor
async function demonstrateBatchProcessing() {
const processor = new BatchProcessor();
// ๐ Create sample data to process
const users = Array.from({ length: 25 }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`,
emoji: ['๐ค', '๐ฅ', '๐ง', '๐จ', '๐ฉ'][i % 5]
}));
// ๐ Define processing function (simulate API calls)
const processUser = async (user: typeof users[0], index: number) => {
// ๐ Simulate variable processing times
const delay = 200 + Math.random() * 800;
await new Promise(resolve => setTimeout(resolve, delay));
// ๐ฒ Simulate 15% failure rate
if (Math.random() < 0.15) {
throw new Error(`Failed to process ${user.name}`);
}
return {
...user,
processed: true,
processedAt: new Date(),
processingTime: delay
};
};
try {
const result = await processor.processBatch(
users,
processUser,
{
batchSize: 5,
maxConcurrency: 3,
progressCallback: (progress) => {
console.log(` ๐ Progress: ${progress.completed}/${progress.total} (${progress.percentage.toFixed(1)}%) - โ
${progress.successful} โ${progress.failed}`);
}
}
);
console.log(`\n๐ Final Results:`);
console.log(` ๐ฆ Total items: ${result.statistics.totalItems}`);
console.log(` โ
Successful: ${result.statistics.successfulItems} (${result.statistics.successRate.toFixed(1)}%)`);
console.log(` โ Failed: ${result.statistics.failedItems}`);
console.log(` ๐ Total batches: ${result.statistics.totalBatches}`);
if (result.errors.length > 0) {
console.log(`\nโ Failed items:`);
result.errors.forEach(error => {
console.log(` - ${error.item.emoji} ${error.item.name}: ${error.error.message}`);
});
}
} catch (error) {
console.error("๐ฅ Batch processing demo failed:", error);
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Ignoring Partial Failures
// โ Dangerous - treating partial success as complete failure
async function badPartialHandling() {
const promises = [
fetch('/api/user/1'),
fetch('/api/user/2'), // This might fail
fetch('/api/user/3')
];
const results = await Promise.allSettled(promises);
// ๐ฅ Wrong: Give up if any failed
const hasFailures = results.some(r => r.status === 'rejected');
if (hasFailures) {
throw new Error("Some requests failed, aborting everything!");
}
// ๐ซ This logic loses valuable partial data
}
// โ
Smart - work with partial success
async function smartPartialHandling() {
const promises = [
fetch('/api/user/1').then(r => r.json()),
fetch('/api/user/2').then(r => r.json()),
fetch('/api/user/3').then(r => r.json())
];
const results = await Promise.allSettled(promises);
// โ
Extract successful results
const users = results
.filter((result): result is PromiseFulfilledResult<any> =>
result.status === 'fulfilled'
)
.map(result => result.value);
// โ
Log failures for debugging but continue with partial data
const failures = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
if (failures.length > 0) {
console.warn(`โ ๏ธ ${failures.length} requests failed, proceeding with ${users.length} users`);
}
return users; // โ
Return what we could successfully get
}
๐คฏ Pitfall 2: Not Handling Processing Errors
// โ Wrong - assuming allSettled results are always processable
async function badResultProcessing() {
const promises = [
Promise.resolve('{"valid": "json"}'),
Promise.resolve('invalid json'),
Promise.resolve('{"another": "valid"}')
];
const results = await Promise.allSettled(promises);
// ๐ฅ This will crash on invalid JSON
const parsed = results.map(result => {
if (result.status === 'fulfilled') {
return JSON.parse(result.value); // ๐ฅ Boom on invalid JSON!
}
return null;
});
}
// โ
Safe - handle processing errors too
async function safeResultProcessing() {
const promises = [
Promise.resolve('{"valid": "json"}'),
Promise.resolve('invalid json'),
Promise.resolve('{"another": "valid"}')
];
const results = await Promise.allSettled(promises);
// โ
Safely process each result
const processed = results.map((result, index) => {
try {
if (result.status === 'fulfilled') {
const parsed = JSON.parse(result.value);
return { success: true, data: parsed, index };
} else {
return { success: false, error: result.reason.message, index };
}
} catch (parseError) {
return { success: false, error: `JSON parse error: ${parseError.message}`, index };
}
});
const successful = processed.filter(p => p.success).map(p => p.data);
const failed = processed.filter(p => !p.success);
console.log(`โ
Parsed ${successful.length} items, ${failed.length} failed`);
return { successful, failed };
}
๐ฏ Pitfall 3: Memory Issues with Large Batches
// โ Memory intensive - processing huge arrays at once
async function memoryIntensiveProcessing(items: any[]) {
// ๐ฅ This loads ALL results into memory simultaneously
const promises = items.map(item => expensiveOperation(item));
const results = await Promise.allSettled(promises);
return results; // Potentially huge array in memory
}
// โ
Memory efficient - process in chunks
async function memoryEfficientProcessing<T, R>(
items: T[],
processor: (item: T) => Promise<R>,
chunkSize: number = 100
): Promise<R[]> {
const allResults: R[] = [];
// ๐ฆ Process in manageable chunks
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
console.log(`๐ฆ Processing chunk ${Math.floor(i/chunkSize) + 1}/${Math.ceil(items.length/chunkSize)}`);
const chunkPromises = chunk.map(processor);
const chunkResults = await Promise.allSettled(chunkPromises);
// โ
Extract successful results and free memory
const successful = chunkResults
.filter((r): r is PromiseFulfilledResult<R> => r.status === 'fulfilled')
.map(r => r.value);
allResults.push(...successful);
// ๐งน Allow garbage collection between chunks
if (i + chunkSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 10));
}
}
return allResults;
}
async function expensiveOperation(item: any): Promise<any> {
// Simulate expensive operation
await new Promise(resolve => setTimeout(resolve, 100));
return { processed: item, timestamp: Date.now() };
}
๐ ๏ธ Best Practices
- ๐ฏ Embrace Partial Success: Design systems that work with partial data rather than all-or-nothing
- ๐ Always Analyze Results: Use helper functions to categorize and summarize settled results
- ๐ Implement Smart Retries: Only retry failed operations, not successful ones
- ๐ Provide Progress Feedback: Keep users informed during long-running batch operations
- ๐งน Manage Memory: Process large datasets in chunks to avoid memory issues
- ๐จ Plan for Edge Cases: Handle scenarios where all operations fail or succeed
- ๐ Log Comprehensive Details: Capture both successes and failures for debugging and analytics
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Distributed File Processing System
Create a robust file processing system that handles multiple file sources with mixed success/failure:
๐ Requirements:
- ๐๏ธ Process files from multiple sources (local, S3, FTP, HTTP)
- โจ Apply multiple transformations (resize, format, validate, encrypt)
- ๐ Generate detailed processing reports with partial success tracking
- ๐ Implement intelligent retry for failed operations only
- ๐พ Save successful results even if some operations fail
- ๐ Provide real-time progress updates
๐ Bonus Points:
- Add file type-specific processing rules
- Implement rollback for partially processed files
- Create a processing queue with priority levels
- Add estimated time remaining calculations
- Generate performance analytics and recommendations
๐ก Solution
๐ Click to see solution
// ๐๏ธ Distributed file processing system with Promise.allSettled
interface FileSource {
id: string;
type: 'local' | 's3' | 'ftp' | 'http';
location: string;
priority: number;
emoji: string;
}
interface FileInfo {
source: FileSource;
filename: string;
size: number;
mimeType: string;
checksum: string;
}
interface ProcessingStep {
name: string;
processor: (file: FileInfo) => Promise<ProcessedFile>;
required: boolean;
emoji: string;
}
interface ProcessedFile {
original: FileInfo;
result: any;
processingTime: number;
step: string;
}
interface ProcessingResult {
file: FileInfo;
steps: {
step: string;
success: boolean;
result?: ProcessedFile;
error?: string;
duration: number;
}[];
overallSuccess: boolean;
completedSteps: number;
totalSteps: number;
}
interface BatchProcessingReport {
totalFiles: number;
fullyProcessed: number;
partiallyProcessed: number;
failed: number;
processingTime: number;
results: ProcessingResult[];
summary: string;
}
class DistributedFileProcessor {
private processingSteps: ProcessingStep[] = [
{
name: 'validate',
processor: this.validateFile.bind(this),
required: true,
emoji: '๐'
},
{
name: 'resize',
processor: this.resizeFile.bind(this),
required: false,
emoji: '๐'
},
{
name: 'format',
processor: this.formatFile.bind(this),
required: true,
emoji: '๐จ'
},
{
name: 'encrypt',
processor: this.encryptFile.bind(this),
required: false,
emoji: '๐'
},
{
name: 'upload',
processor: this.uploadFile.bind(this),
required: true,
emoji: 'โ๏ธ'
}
];
// ๐ File validation step
private async validateFile(file: FileInfo): Promise<ProcessedFile> {
const startTime = Date.now();
await this.delay(100 + Math.random() * 200);
// ๐ฒ 10% chance of validation failure
if (Math.random() < 0.1) {
throw new Error(`File validation failed: corrupted or invalid format`);
}
const processingTime = Date.now() - startTime;
return {
original: file,
result: { validated: true, checks: ['format', 'size', 'integrity'] },
processingTime,
step: 'validate'
};
}
// ๐ File resize step
private async resizeFile(file: FileInfo): Promise<ProcessedFile> {
const startTime = Date.now();
await this.delay(300 + Math.random() * 500);
// ๐ฒ 5% chance of resize failure
if (Math.random() < 0.05) {
throw new Error(`Resize failed: unsupported image format`);
}
const processingTime = Date.now() - startTime;
return {
original: file,
result: { resized: true, dimensions: { width: 800, height: 600 } },
processingTime,
step: 'resize'
};
}
// ๐จ File format conversion step
private async formatFile(file: FileInfo): Promise<ProcessedFile> {
const startTime = Date.now();
await this.delay(200 + Math.random() * 400);
// ๐ฒ 8% chance of format failure
if (Math.random() < 0.08) {
throw new Error(`Format conversion failed: codec not available`);
}
const processingTime = Date.now() - startTime;
return {
original: file,
result: { formatted: true, outputFormat: 'webp', compression: 85 },
processingTime,
step: 'format'
};
}
// ๐ File encryption step
private async encryptFile(file: FileInfo): Promise<ProcessedFile> {
const startTime = Date.now();
await this.delay(150 + Math.random() * 300);
// ๐ฒ 3% chance of encryption failure
if (Math.random() < 0.03) {
throw new Error(`Encryption failed: key generation error`);
}
const processingTime = Date.now() - startTime;
return {
original: file,
result: { encrypted: true, algorithm: 'AES-256', keyId: 'key-' + Math.random().toString(36).substr(2, 9) },
processingTime,
step: 'encrypt'
};
}
// โ๏ธ File upload step
private async uploadFile(file: FileInfo): Promise<ProcessedFile> {
const startTime = Date.now();
await this.delay(400 + Math.random() * 600);
// ๐ฒ 12% chance of upload failure
if (Math.random() < 0.12) {
throw new Error(`Upload failed: network timeout or storage full`);
}
const processingTime = Date.now() - startTime;
return {
original: file,
result: { uploaded: true, url: `https://cdn.example.com/${file.filename}`, etag: 'etag-' + Math.random().toString(36).substr(2, 12) },
processingTime,
step: 'upload'
};
}
// ๐ฏ Process single file through all steps
async processFile(file: FileInfo): Promise<ProcessingResult> {
console.log(`๐ Processing ${file.source.emoji} ${file.filename}...`);
const stepPromises = this.processingSteps.map(async (step) => {
const startTime = Date.now();
try {
const result = await step.processor(file);
const duration = Date.now() - startTime;
return {
step: step.name,
success: true,
result,
duration
};
} catch (error) {
const duration = Date.now() - startTime;
return {
step: step.name,
success: false,
error: error.message,
duration
};
}
});
// ๐ญ Wait for all processing steps to complete
const settledResults = await Promise.allSettled(stepPromises);
// ๐ Process the results
const steps = settledResults.map(result =>
result.status === 'fulfilled' ? result.value : {
step: 'unknown',
success: false,
error: 'Step processing failed',
duration: 0
}
);
const completedSteps = steps.filter(s => s.success).length;
const requiredSteps = this.processingSteps.filter(s => s.required);
const completedRequiredSteps = steps.filter(s =>
s.success && requiredSteps.some(rs => rs.name === s.step)
).length;
const overallSuccess = completedRequiredSteps === requiredSteps.length;
console.log(` ${overallSuccess ? 'โ
' : 'โ ๏ธ'} ${file.filename}: ${completedSteps}/${this.processingSteps.length} steps completed`);
return {
file,
steps,
overallSuccess,
completedSteps,
totalSteps: this.processingSteps.length
};
}
// ๐ Process multiple files with detailed reporting
async processBatch(files: FileInfo[]): Promise<BatchProcessingReport> {
console.log(`๐ Starting batch processing of ${files.length} files...\n`);
const startTime = Date.now();
// ๐ญ Process all files concurrently with allSettled
const filePromises = files.map(file => this.processFile(file));
const settledResults = await Promise.allSettled(filePromises);
// ๐ Extract successful processing results
const results: ProcessingResult[] = settledResults
.filter((result): result is PromiseFulfilledResult<ProcessingResult> =>
result.status === 'fulfilled'
)
.map(result => result.value);
// ๐ Calculate statistics
const fullyProcessed = results.filter(r => r.overallSuccess).length;
const partiallyProcessed = results.filter(r =>
!r.overallSuccess && r.completedSteps > 0
).length;
const failed = results.filter(r => r.completedSteps === 0).length;
const processingTime = Date.now() - startTime;
const summary = `${fullyProcessed} fully processed, ${partiallyProcessed} partially processed, ${failed} failed`;
console.log(`\n๐ Batch processing complete in ${processingTime}ms!`);
console.log(`๐ Results: ${summary}`);
return {
totalFiles: files.length,
fullyProcessed,
partiallyProcessed,
failed,
processingTime,
results,
summary
};
}
// ๐ Display detailed batch report
displayDetailedReport(report: BatchProcessingReport): void {
console.log(`\n๐ Detailed Processing Report`);
console.log(`${'='.repeat(50)}`);
console.log(`\n๐ Overview:`);
console.log(` ๐ Total files: ${report.totalFiles}`);
console.log(` โ
Fully processed: ${report.fullyProcessed}`);
console.log(` โ ๏ธ Partially processed: ${report.partiallyProcessed}`);
console.log(` โ Failed: ${report.failed}`);
console.log(` โฑ๏ธ Total time: ${report.processingTime}ms`);
console.log(` ๐ Success rate: ${((report.fullyProcessed / report.totalFiles) * 100).toFixed(1)}%`);
console.log(`\n๐ File Details:`);
report.results.forEach((result, index) => {
const status = result.overallSuccess ? 'โ
SUCCESS' :
result.completedSteps > 0 ? 'โ ๏ธ PARTIAL' : 'โ FAILED';
console.log(`\n${index + 1}. ${result.file.source.emoji} ${result.file.filename}`);
console.log(` Status: ${status} (${result.completedSteps}/${result.totalSteps} steps)`);
result.steps.forEach(step => {
const stepInfo = this.processingSteps.find(s => s.name === step.step);
const emoji = step.success ? 'โ
' : 'โ';
const required = stepInfo?.required ? ' [REQUIRED]' : '';
console.log(` ${emoji} ${stepInfo?.emoji} ${step.step}${required}: ${step.duration}ms`);
if (!step.success && step.error) {
console.log(` Error: ${step.error}`);
}
});
});
// ๐ง Processing step analysis
console.log(`\n๐ง Step Analysis:`);
this.processingSteps.forEach(step => {
const stepResults = report.results.flatMap(r =>
r.steps.filter(s => s.step === step.name)
);
const successful = stepResults.filter(s => s.success).length;
const total = stepResults.length;
const avgTime = stepResults.reduce((sum, s) => sum + s.duration, 0) / total;
console.log(` ${step.emoji} ${step.name}: ${successful}/${total} (${((successful/total)*100).toFixed(1)}%) - avg ${avgTime.toFixed(0)}ms`);
});
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Demo the distributed file processor
async function demonstrateFileProcessing() {
const processor = new DistributedFileProcessor();
// ๐ Create sample files from different sources
const files: FileInfo[] = [
{
source: { id: 's3-1', type: 's3', location: 'bucket-1', priority: 1, emoji: 'โ๏ธ' },
filename: 'photo1.jpg',
size: 2048576,
mimeType: 'image/jpeg',
checksum: 'abc123'
},
{
source: { id: 'local-1', type: 'local', location: '/uploads', priority: 2, emoji: '๐ป' },
filename: 'document.pdf',
size: 1024000,
mimeType: 'application/pdf',
checksum: 'def456'
},
{
source: { id: 'ftp-1', type: 'ftp', location: 'ftp.example.com', priority: 3, emoji: '๐ก' },
filename: 'video.mp4',
size: 10485760,
mimeType: 'video/mp4',
checksum: 'ghi789'
},
{
source: { id: 'http-1', type: 'http', location: 'https://api.example.com', priority: 4, emoji: '๐' },
filename: 'data.json',
size: 512000,
mimeType: 'application/json',
checksum: 'jkl012'
},
{
source: { id: 's3-2', type: 's3', location: 'bucket-2', priority: 1, emoji: 'โ๏ธ' },
filename: 'image.png',
size: 3072000,
mimeType: 'image/png',
checksum: 'mno345'
}
];
try {
// ๐ Process the batch
const report = await processor.processBatch(files);
// ๐ Display detailed results
processor.displayDetailedReport(report);
} catch (error) {
console.error("๐ฅ File processing demo failed:", error);
}
}
// ๐ Run the demo
demonstrateFileProcessing();
๐ Key Takeaways
Youโve mastered Promise.allSettled! Hereโs what you can now do:
- โ Handle mixed success/failure scenarios gracefully without losing partial data ๐ช
- โ Build resilient batch processing systems that work with partial results ๐ก๏ธ
- โ Implement comprehensive error reporting while continuing operations ๐ฏ
- โ Create fault-tolerant applications that donโt fail at the first obstacle ๐
- โ Design systems that embrace partial success rather than all-or-nothing approaches ๐
Remember: In the real world, some operations will always fail. Promise.allSettled helps you build systems that keep working despite these inevitable failures! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered Promise.allSettled in TypeScript!
Hereโs what to explore next:
- ๐ป Practice with the distributed file processing exercise above
- ๐๏ธ Build a comprehensive data validation system with partial success tracking
- ๐ Explore advanced Promise patterns like custom Promise schedulers
- ๐ Learn about AsyncIterables and Observables for streaming data processing
Remember: Resilience isnโt about never failing - itโs about continuing to work when parts of your system do fail! ๐
Happy coding! ๐๐โจ