Prerequisites
- Basic TypeScript syntax and types 📝
- Understanding of Promises and async programming 🤝
- Promise chaining and Promise.all fundamentals 🔗
What you'll learn
- Master async/await syntax for clean, readable async code ⚡
- Handle complex async flows with elegant error management 🛡️
- Combine async/await with parallel operations and performance optimization 🚀
- Build sophisticated async applications with modern patterns 🏗️
🎯 Introduction
Welcome to the modern era of async programming with async/await! 🎉 In this guide, we’ll explore how to write async code that reads like synchronous code while maintaining all the power of asynchronous operations.
You’ll discover how async/await transforms complex promise chains into clean, linear code that’s easy to read, debug, and maintain. Whether you’re building APIs 🌐, processing data streams 📊, or orchestrating complex workflows 🏭, mastering async/await is essential for writing professional-grade TypeScript applications.
By the end of this tutorial, you’ll be writing async code that’s not just functional, but elegantly crafted and a joy to work with! ⚡ Let’s dive in! 🏊♂️
📚 Understanding Async/Await
🤔 What is Async/Await?
Async/await is like having a magic wand that makes async code look and feel like regular synchronous code ✨. Think of it as syntactic sugar that transforms callback hell and promise chains into clean, readable functions that flow naturally from top to bottom.
In TypeScript terms, async/await provides:
- ✨ Synchronous-looking syntax - write async code that reads like sync code
- 🚀 Better error handling - use try/catch blocks naturally
- 🛡️ Type safety - TypeScript infers return types automatically
- 📦 Clean debugging - step through async code like regular code
💡 Why Use Async/Await?
Here’s why async/await is a game-changer:
- Readability 📖: Code flows naturally from top to bottom
- Error Handling 🚨: Use familiar try/catch patterns
- Debugging 🔍: Easier to set breakpoints and trace execution
- Maintainability 🔧: Less complex than promise chains
- Team Collaboration 👥: More intuitive for developers
Real-world comparison: Instead of .then().then().catch()
chains, you get clean, linear code that tells a story! 📚
🔧 Basic Async/Await Patterns
📝 From Promises to Async/Await
Let’s see the transformation from promises to async/await:
// ❌ Old way: Promise chains
const fetchUserDataOldWay = (userId: string): Promise<string> => {
return fetchUser(userId)
.then((user) => {
console.log('👤 User fetched:', user.name);
return fetchUserProfile(user.id);
})
.then((profile) => {
console.log('📊 Profile fetched:', profile.bio);
return fetchUserPreferences(profile.userId);
})
.then((preferences) => {
console.log('⚙️ Preferences fetched:', preferences.theme);
return `Welcome ${preferences.displayName}!`;
})
.catch((error) => {
console.error('💥 Error:', error.message);
throw error;
});
};
// ✅ New way: Async/await
const fetchUserDataNewWay = async (userId: string): Promise<string> => {
try {
// 🎯 Clean, linear flow - reads like a story!
const user = await fetchUser(userId);
console.log('👤 User fetched:', user.name);
const profile = await fetchUserProfile(user.id);
console.log('📊 Profile fetched:', profile.bio);
const preferences = await fetchUserPreferences(profile.userId);
console.log('⚙️ Preferences fetched:', preferences.theme);
return `Welcome ${preferences.displayName}!`;
} catch (error) {
console.error('💥 Error:', error.message);
throw error;
}
};
// 🏗️ Type definitions for clarity
interface User {
id: string;
name: string;
email: string;
}
interface UserProfile {
userId: string;
bio: string;
avatar: string;
joinedAt: Date;
}
interface UserPreferences {
userId: string;
theme: 'light' | 'dark';
language: string;
displayName: string;
notifications: boolean;
}
// 🛠️ Mock API functions
const fetchUser = async (userId: string): Promise<User> => {
await new Promise(resolve => setTimeout(resolve, 200));
return {
id: userId,
name: 'John Doe',
email: '[email protected]'
};
};
const fetchUserProfile = async (userId: string): Promise<UserProfile> => {
await new Promise(resolve => setTimeout(resolve, 150));
return {
userId,
bio: 'Software engineer passionate about TypeScript',
avatar: 'https://avatar.example.com/john.jpg',
joinedAt: new Date('2023-01-15')
};
};
const fetchUserPreferences = async (userId: string): Promise<UserPreferences> => {
await new Promise(resolve => setTimeout(resolve, 100));
return {
userId,
theme: 'dark',
language: 'en',
displayName: 'John D.',
notifications: true
};
};
🔄 Type Inference and Return Types
// 🎯 TypeScript automatically infers async function return types
// async functions always return Promise<T>
// ✅ TypeScript infers: Promise<string>
const getGreeting = async (name: string) => {
await new Promise(resolve => setTimeout(resolve, 100));
return `Hello, ${name}!`; // Returns string, wrapped in Promise<string>
};
// ✅ Explicit return type annotation (recommended for public APIs)
const calculateTotal = async (items: number[]): Promise<number> => {
await new Promise(resolve => setTimeout(resolve, 50));
return items.reduce((sum, item) => sum + item, 0);
};
// ✅ Complex return types work seamlessly
interface ProcessingResult {
success: boolean;
data: any;
processedAt: Date;
duration: number;
}
const processData = async (input: any[]): Promise<ProcessingResult> => {
const startTime = Date.now();
// 🔄 Simulate processing
await new Promise(resolve => setTimeout(resolve, 300));
const processedData = input.map(item => ({ ...item, processed: true }));
return {
success: true,
data: processedData,
processedAt: new Date(),
duration: Date.now() - startTime
};
};
// 🎯 Using the async functions with proper typing
const demonstrateTyping = async () => {
// ✨ TypeScript knows these are Promises
const greeting = await getGreeting('TypeScript Developer'); // string
const total = await calculateTotal([10, 20, 30, 40]); // number
const result = await processData([{ id: 1 }, { id: 2 }]); // ProcessingResult
console.log('✨ Greeting:', greeting);
console.log('🧮 Total:', total);
console.log('📊 Result:', result.success, result.duration + 'ms');
};
🚨 Advanced Error Handling
🛡️ Sophisticated Try/Catch Patterns
// 🎯 Advanced error handling strategies with async/await
interface ApiError {
code: string;
message: string;
statusCode: number;
timestamp: Date;
}
interface ValidationError {
field: string;
message: string;
value: any;
}
// 🚨 Custom error classes for better error handling
class UserNotFoundError extends Error {
constructor(userId: string) {
super(`User with ID ${userId} not found`);
this.name = 'UserNotFoundError';
}
}
class ValidationError extends Error {
public errors: ValidationError[];
constructor(errors: ValidationError[]) {
super(`Validation failed: ${errors.map(e => e.message).join(', ')}`);
this.name = 'ValidationError';
this.errors = errors;
}
}
// 🏥 Comprehensive error handling with recovery strategies
const robustUserDataLoader = async (userId: string): Promise<{
user: User | null;
profile: UserProfile | null;
preferences: UserPreferences | null;
errors: string[];
}> => {
const errors: string[] = [];
let user: User | null = null;
let profile: UserProfile | null = null;
let preferences: UserPreferences | null = null;
// 👤 Step 1: Fetch user (critical - if this fails, stop)
try {
console.log('👤 Fetching user...');
user = await fetchUserWithValidation(userId);
console.log('✅ User loaded successfully');
} catch (error) {
if (error instanceof UserNotFoundError) {
errors.push(`User not found: ${userId}`);
return { user, profile, preferences, errors }; // Early return
}
if (error instanceof ValidationError) {
errors.push(`User validation failed: ${error.message}`);
return { user, profile, preferences, errors };
}
// 🚨 Unexpected error
console.error('💥 Unexpected error fetching user:', error);
errors.push('Failed to fetch user due to system error');
return { user, profile, preferences, errors };
}
// 📊 Step 2: Fetch profile (optional - continue if fails)
try {
console.log('📊 Fetching user profile...');
profile = await fetchUserProfileWithRetry(user.id);
console.log('✅ Profile loaded successfully');
} catch (error) {
console.warn('⚠️ Profile loading failed, using defaults:', error.message);
errors.push(`Profile loading failed: ${error.message}`);
// Continue execution - profile is optional
}
// ⚙️ Step 3: Fetch preferences (optional - continue if fails)
try {
console.log('⚙️ Fetching user preferences...');
preferences = await fetchUserPreferencesWithFallback(user.id);
console.log('✅ Preferences loaded successfully');
} catch (error) {
console.warn('⚠️ Preferences loading failed, using defaults:', error.message);
errors.push(`Preferences loading failed: ${error.message}`);
// Continue execution - preferences are optional
}
return { user, profile, preferences, errors };
};
// 🔄 Retry logic with exponential backoff
const fetchUserProfileWithRetry = async (
userId: string,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<UserProfile> => {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`🔄 Profile fetch attempt ${attempt}/${maxRetries}`);
// 🎲 Simulate unreliable API
if (Math.random() < 0.4) { // 40% failure rate for demo
throw new Error('Profile service temporarily unavailable');
}
return await fetchUserProfile(userId);
} catch (error) {
lastError = error as Error;
console.warn(`❌ Attempt ${attempt} failed:`, error.message);
if (attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
console.log(`⏳ Waiting ${delay}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Profile fetch failed after ${maxRetries} attempts: ${lastError!.message}`);
};
// 🏥 Fallback strategies for non-critical data
const fetchUserPreferencesWithFallback = async (userId: string): Promise<UserPreferences> => {
try {
// 🎯 Try primary preferences service
return await fetchUserPreferences(userId);
} catch (primaryError) {
console.warn('⚠️ Primary preferences service failed, trying backup...');
try {
// 🔄 Try backup service
return await fetchUserPreferencesFromBackup(userId);
} catch (backupError) {
console.warn('⚠️ Backup preferences service failed, using defaults...');
// 🛟 Return sensible defaults
return {
userId,
theme: 'light',
language: 'en',
displayName: 'User',
notifications: true
};
}
}
};
// 🛠️ Helper functions with validation
const fetchUserWithValidation = async (userId: string): Promise<User> => {
// ✅ Input validation
if (!userId || userId.trim() === '') {
throw new ValidationError([{
field: 'userId',
message: 'User ID cannot be empty',
value: userId
}]);
}
if (userId.length < 3) {
throw new ValidationError([{
field: 'userId',
message: 'User ID must be at least 3 characters',
value: userId
}]);
}
// 🎲 Simulate user not found
if (userId === 'notfound') {
throw new UserNotFoundError(userId);
}
return await fetchUser(userId);
};
const fetchUserPreferencesFromBackup = async (userId: string): Promise<UserPreferences> => {
await new Promise(resolve => setTimeout(resolve, 200));
// 🎲 Simulate backup service reliability
if (Math.random() < 0.2) { // 20% failure rate
throw new Error('Backup service unavailable');
}
return {
userId,
theme: 'dark', // Different defaults from backup
language: 'en',
displayName: 'User (Backup)',
notifications: false
};
};
🚀 Parallel Operations with Async/Await
⚡ Combining Async/Await with Promise.all
// 🎯 Best of both worlds: async/await readability + Promise.all performance
interface DashboardData {
user: User;
stats: UserStats;
notifications: Notification[];
recentActivity: Activity[];
settings: UserSettings;
}
interface UserStats {
loginCount: number;
lastActive: Date;
achievementsCount: number;
friendsCount: number;
}
interface Notification {
id: string;
message: string;
type: 'info' | 'warning' | 'success';
createdAt: Date;
isRead: boolean;
}
interface Activity {
id: string;
action: string;
timestamp: Date;
metadata: Record<string, any>;
}
interface UserSettings {
privacy: 'public' | 'private';
emailNotifications: boolean;
pushNotifications: boolean;
twoFactorEnabled: boolean;
}
// 🏗️ Optimized dashboard loading with parallel + sequential operations
const loadUserDashboard = async (userId: string): Promise<DashboardData> => {
try {
console.log('🚀 Loading user dashboard...');
// 🎯 Step 1: Get user first (required for other calls)
const user = await fetchUser(userId);
console.log('👤 User loaded:', user.name);
// 🚀 Step 2: Load everything else in parallel
console.log('⚡ Loading dashboard data in parallel...');
const [stats, notifications, recentActivity, settings] = await Promise.all([
fetchUserStats(userId),
fetchUserNotifications(userId),
fetchRecentActivity(userId),
fetchUserSettings(userId)
]);
console.log('✅ Dashboard data loaded successfully!');
return {
user,
stats,
notifications,
recentActivity,
settings
};
} catch (error) {
console.error('💥 Dashboard loading failed:', error);
throw new Error(`Failed to load dashboard: ${error.message}`);
}
};
// 🔄 Batch processing with controlled concurrency
const processBatchItems = async <T, R>(
items: T[],
processor: (item: T) => Promise<R>,
batchSize: number = 5
): Promise<R[]> => {
const results: R[] = [];
console.log(`📦 Processing ${items.length} items in batches of ${batchSize}`);
// 🔪 Split into batches
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
console.log(`🔄 Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(items.length / batchSize)}`);
// 🚀 Process current batch in parallel
const batchResults = await Promise.all(
batch.map(item => processor(item))
);
results.push(...batchResults);
console.log(`✅ Batch completed. Processed ${results.length}/${items.length} items`);
}
return results;
};
// 🛠️ Mock API functions for dashboard
const fetchUserStats = async (userId: string): Promise<UserStats> => {
await new Promise(resolve => setTimeout(resolve, 300));
return {
loginCount: 142,
lastActive: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
achievementsCount: 23,
friendsCount: 89
};
};
const fetchUserNotifications = async (userId: string): Promise<Notification[]> => {
await new Promise(resolve => setTimeout(resolve, 200));
return [
{
id: 'notif_1',
message: 'Your profile was updated successfully! 🎉',
type: 'success',
createdAt: new Date(Date.now() - 3600000), // 1 hour ago
isRead: false
},
{
id: 'notif_2',
message: 'New friend request from Sarah 👋',
type: 'info',
createdAt: new Date(Date.now() - 7200000), // 2 hours ago
isRead: true
}
];
};
const fetchRecentActivity = async (userId: string): Promise<Activity[]> => {
await new Promise(resolve => setTimeout(resolve, 250));
return [
{
id: 'activity_1',
action: 'Updated profile picture',
timestamp: new Date(Date.now() - 1800000), // 30 minutes ago
metadata: { section: 'profile', change: 'avatar' }
},
{
id: 'activity_2',
action: 'Completed TypeScript tutorial',
timestamp: new Date(Date.now() - 3600000), // 1 hour ago
metadata: { course: 'typescript-fundamentals', score: 95 }
}
];
};
const fetchUserSettings = async (userId: string): Promise<UserSettings> => {
await new Promise(resolve => setTimeout(resolve, 150));
return {
privacy: 'private',
emailNotifications: true,
pushNotifications: false,
twoFactorEnabled: true
};
};
🌊 Advanced Async Patterns
🔄 Async Iterators and Generators
// 🎯 Advanced async patterns for streaming and data processing
interface DataChunk {
id: string;
data: any[];
timestamp: Date;
hasMore: boolean;
}
// 🌊 Async generator for streaming data
async function* streamDataChunks(query: string, chunkSize: number = 100): AsyncGenerator<DataChunk> {
let offset = 0;
let hasMore = true;
while (hasMore) {
console.log(`📡 Fetching data chunk at offset ${offset}...`);
// 🔍 Fetch data chunk
const chunk = await fetchDataChunk(query, offset, chunkSize);
yield chunk;
// 📊 Update state for next iteration
offset += chunkSize;
hasMore = chunk.hasMore;
console.log(`✅ Yielded chunk with ${chunk.data.length} items`);
}
console.log('🏁 Stream completed');
}
// 🔄 Process streaming data with async iteration
const processStreamingData = async (query: string) => {
const processedItems: any[] = [];
let totalProcessed = 0;
console.log('🌊 Starting streaming data processing...');
// 🎯 Use for await...of to consume async generator
for await (const chunk of streamDataChunks(query)) {
console.log(`🔄 Processing chunk ${chunk.id} with ${chunk.data.length} items`);
// 🚀 Process items in parallel within each chunk
const processedChunk = await Promise.all(
chunk.data.map(item => processDataItem(item))
);
processedItems.push(...processedChunk);
totalProcessed += processedChunk.length;
console.log(`✅ Processed chunk. Total: ${totalProcessed} items`);
// 💤 Optional: Add delay between chunks to avoid overwhelming APIs
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(`🎉 Streaming processing completed! Total items: ${totalProcessed}`);
return processedItems;
};
// 🎯 Concurrent async operations with limits
class AsyncQueue {
private running = 0;
private queue: Array<() => Promise<any>> = [];
constructor(private maxConcurrency: number = 3) {}
async add<T>(operation: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
this.running++;
const result = await operation();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.processNext();
}
});
this.processNext();
});
}
private processNext() {
if (this.running < this.maxConcurrency && this.queue.length > 0) {
const operation = this.queue.shift()!;
operation();
}
}
}
// 🚀 Example: Concurrent file processing with queue
const processFilesWithQueue = async (fileUrls: string[]) => {
const queue = new AsyncQueue(3); // Max 3 concurrent operations
const results: any[] = [];
console.log(`📁 Processing ${fileUrls.length} files with max 3 concurrent operations`);
// 🔄 Add all operations to queue
const promises = fileUrls.map((url, index) =>
queue.add(async () => {
console.log(`🔄 Processing file ${index + 1}: ${url}`);
const result = await processFile(url);
console.log(`✅ Completed file ${index + 1}`);
return result;
})
);
// ⏳ Wait for all operations to complete
const processedFiles = await Promise.all(promises);
console.log('🎉 All files processed successfully!');
return processedFiles;
};
// 🛠️ Helper functions for advanced patterns
const fetchDataChunk = async (query: string, offset: number, limit: number): Promise<DataChunk> => {
await new Promise(resolve => setTimeout(resolve, 200));
const mockData = Array.from({ length: limit }, (_, i) => ({
id: `item_${offset + i}`,
value: Math.random() * 100,
category: ['A', 'B', 'C'][Math.floor(Math.random() * 3)]
}));
return {
id: `chunk_${offset}`,
data: mockData,
timestamp: new Date(),
hasMore: offset + limit < 1000 // Simulate 1000 total items
};
};
const processDataItem = async (item: any) => {
// 🔄 Simulate processing time
await new Promise(resolve => setTimeout(resolve, Math.random() * 50));
return {
...item,
processed: true,
processedAt: new Date()
};
};
const processFile = async (url: string) => {
// 🔄 Simulate file processing
const processingTime = 500 + Math.random() * 1000; // 0.5-1.5 seconds
await new Promise(resolve => setTimeout(resolve, processingTime));
return {
url,
size: Math.floor(Math.random() * 1000000), // Random file size
processedAt: new Date(),
status: 'success'
};
};
🎯 Real-World Application: Content Management System
📝 Complete CMS with Async/Await
// 🎯 Comprehensive CMS example with modern async patterns
interface Article {
id: string;
title: string;
content: string;
authorId: string;
categoryId: string;
tags: string[];
status: 'draft' | 'published' | 'archived';
publishedAt?: Date;
createdAt: Date;
updatedAt: Date;
}
interface Author {
id: string;
name: string;
email: string;
bio: string;
avatar: string;
}
interface Category {
id: string;
name: string;
description: string;
articleCount: number;
}
interface ArticleWithDetails {
article: Article;
author: Author;
category: Category;
relatedArticles: Article[];
viewCount: number;
averageRating: number;
}
interface PublishingResult {
success: boolean;
article: Article;
seoScore: number;
socialMediaPosted: boolean;
emailsSent: number;
errors: string[];
}
// 📚 CMS Article Management System
class ArticleManager {
private async validateArticle(article: Partial<Article>): Promise<void> {
const errors: string[] = [];
if (!article.title || article.title.trim().length < 5) {
errors.push('Title must be at least 5 characters long');
}
if (!article.content || article.content.trim().length < 100) {
errors.push('Content must be at least 100 characters long');
}
if (!article.authorId) {
errors.push('Author ID is required');
}
if (!article.categoryId) {
errors.push('Category ID is required');
}
if (errors.length > 0) {
throw new ValidationError(errors.map(message => ({
field: 'article',
message,
value: article
})));
}
// 🔍 Async validation - check if author exists
try {
await this.fetchAuthor(article.authorId!);
} catch (error) {
throw new ValidationError([{
field: 'authorId',
message: `Author not found: ${article.authorId}`,
value: article.authorId
}]);
}
// 🔍 Async validation - check if category exists
try {
await this.fetchCategory(article.categoryId!);
} catch (error) {
throw new ValidationError([{
field: 'categoryId',
message: `Category not found: ${article.categoryId}`,
value: article.categoryId
}]);
}
}
// 📝 Create new article with comprehensive validation
async createArticle(articleData: Omit<Article, 'id' | 'createdAt' | 'updatedAt'>): Promise<Article> {
try {
console.log('📝 Creating new article...');
// ✅ Step 1: Validate article data
await this.validateArticle(articleData);
// 🎯 Step 2: Generate ID and timestamps
const article: Article = {
id: `article_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
...articleData,
createdAt: new Date(),
updatedAt: new Date()
};
// 💾 Step 3: Save to database
await this.saveArticle(article);
console.log('✅ Article created successfully:', article.id);
return article;
} catch (error) {
console.error('💥 Article creation failed:', error);
throw error;
}
}
// 📊 Load complete article with all related data
async loadArticleWithDetails(articleId: string): Promise<ArticleWithDetails> {
try {
console.log('📊 Loading article with details...');
// 🎯 Step 1: Load base article
const article = await this.fetchArticle(articleId);
// 🚀 Step 2: Load related data in parallel
const [author, category, relatedArticles, viewCount, averageRating] = await Promise.all([
this.fetchAuthor(article.authorId),
this.fetchCategory(article.categoryId),
this.fetchRelatedArticles(articleId, 5),
this.getArticleViewCount(articleId),
this.getArticleRating(articleId)
]);
console.log('✅ Article details loaded successfully');
return {
article,
author,
category,
relatedArticles,
viewCount,
averageRating
};
} catch (error) {
console.error('💥 Failed to load article details:', error);
throw error;
}
}
// 🌐 Publish article with comprehensive workflow
async publishArticle(articleId: string): Promise<PublishingResult> {
const errors: string[] = [];
let seoScore = 0;
let socialMediaPosted = false;
let emailsSent = 0;
try {
console.log('🌐 Starting article publishing workflow...');
// 📝 Step 1: Load and validate article
const article = await this.fetchArticle(articleId);
if (article.status === 'published') {
throw new Error('Article is already published');
}
// ✅ Step 2: Pre-publishing validation
await this.validateArticleForPublishing(article);
// 🎯 Step 3: Update article status
const publishedArticle = {
...article,
status: 'published' as const,
publishedAt: new Date(),
updatedAt: new Date()
};
await this.saveArticle(publishedArticle);
console.log('✅ Article status updated to published');
// 🚀 Step 4: Parallel post-publishing tasks
const publishingTasks = await Promise.allSettled([
this.analyzeSEO(publishedArticle),
this.postToSocialMedia(publishedArticle),
this.sendNotificationEmails(publishedArticle)
]);
// 📊 Process results from parallel tasks
const [seoResult, socialResult, emailResult] = publishingTasks;
if (seoResult.status === 'fulfilled') {
seoScore = seoResult.value;
} else {
errors.push(`SEO analysis failed: ${seoResult.reason.message}`);
}
if (socialResult.status === 'fulfilled') {
socialMediaPosted = socialResult.value;
} else {
errors.push(`Social media posting failed: ${socialResult.reason.message}`);
}
if (emailResult.status === 'fulfilled') {
emailsSent = emailResult.value;
} else {
errors.push(`Email notifications failed: ${emailResult.reason.message}`);
}
console.log('🎉 Article publishing workflow completed!');
return {
success: true,
article: publishedArticle,
seoScore,
socialMediaPosted,
emailsSent,
errors
};
} catch (error) {
console.error('💥 Article publishing failed:', error);
return {
success: false,
article: await this.fetchArticle(articleId),
seoScore: 0,
socialMediaPosted: false,
emailsSent: 0,
errors: [error.message, ...errors]
};
}
}
// 🔍 Search articles with advanced filtering
async searchArticles(
query: string,
filters: {
authorId?: string;
categoryId?: string;
status?: Article['status'];
tags?: string[];
dateFrom?: Date;
dateTo?: Date;
} = {},
pagination: { page: number; limit: number } = { page: 1, limit: 10 }
): Promise<{
articles: ArticleWithDetails[];
totalCount: number;
hasMore: boolean;
}> {
try {
console.log('🔍 Searching articles with filters...');
// 🎯 Step 1: Search for article IDs with filters
const articleIds = await this.searchArticleIds(query, filters, pagination);
// 🚀 Step 2: Load detailed article data in parallel
const articles = await Promise.all(
articleIds.map(id => this.loadArticleWithDetails(id))
);
// 📊 Step 3: Get total count for pagination
const totalCount = await this.getSearchResultCount(query, filters);
const hasMore = (pagination.page * pagination.limit) < totalCount;
console.log(`✅ Found ${articles.length} articles (${totalCount} total)`);
return {
articles,
totalCount,
hasMore
};
} catch (error) {
console.error('💥 Article search failed:', error);
throw error;
}
}
// 🛠️ Private helper methods
private async fetchArticle(id: string): Promise<Article> {
await new Promise(resolve => setTimeout(resolve, 100));
if (id === 'notfound') {
throw new Error(`Article not found: ${id}`);
}
return {
id,
title: 'Understanding Async/Await in TypeScript',
content: 'This is a comprehensive guide to mastering async/await patterns...',
authorId: 'author_1',
categoryId: 'typescript',
tags: ['typescript', 'async', 'programming'],
status: 'draft',
createdAt: new Date(Date.now() - 24 * 60 * 60 * 1000), // 1 day ago
updatedAt: new Date()
};
}
private async fetchAuthor(id: string): Promise<Author> {
await new Promise(resolve => setTimeout(resolve, 80));
return {
id,
name: 'Jane Developer',
email: '[email protected]',
bio: 'Senior TypeScript developer and technical writer',
avatar: 'https://avatar.example.com/jane.jpg'
};
}
private async fetchCategory(id: string): Promise<Category> {
await new Promise(resolve => setTimeout(resolve, 60));
return {
id,
name: 'TypeScript',
description: 'Articles about TypeScript programming',
articleCount: 47
};
}
private async saveArticle(article: Article): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 150));
console.log('💾 Article saved to database:', article.id);
}
private async validateArticleForPublishing(article: Article): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 100));
if (article.content.length < 500) {
throw new Error('Article content too short for publishing (minimum 500 characters)');
}
}
private async analyzeSEO(article: Article): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 300));
let score = 50;
if (article.title.length > 30) score += 15;
if (article.tags.length >= 3) score += 20;
if (article.content.length > 1000) score += 15;
return Math.min(score, 100);
}
private async postToSocialMedia(article: Article): Promise<boolean> {
await new Promise(resolve => setTimeout(resolve, 400));
// 🎲 Simulate occasional social media failures
if (Math.random() < 0.1) {
throw new Error('Social media API temporarily unavailable');
}
console.log('📱 Posted to social media platforms');
return true;
}
private async sendNotificationEmails(article: Article): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 250));
const subscriberCount = Math.floor(Math.random() * 1000) + 100;
console.log(`📧 Sent notifications to ${subscriberCount} subscribers`);
return subscriberCount;
}
private async fetchRelatedArticles(articleId: string, limit: number): Promise<Article[]> {
await new Promise(resolve => setTimeout(resolve, 120));
return Array.from({ length: limit }, (_, i) => ({
id: `related_${i + 1}`,
title: `Related Article ${i + 1}`,
content: 'Related content...',
authorId: 'author_1',
categoryId: 'typescript',
tags: ['typescript'],
status: 'published' as const,
publishedAt: new Date(Date.now() - (i + 1) * 24 * 60 * 60 * 1000),
createdAt: new Date(Date.now() - (i + 2) * 24 * 60 * 60 * 1000),
updatedAt: new Date()
}));
}
private async getArticleViewCount(articleId: string): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 50));
return Math.floor(Math.random() * 5000) + 100;
}
private async getArticleRating(articleId: string): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 40));
return Math.round((Math.random() * 2 + 3) * 10) / 10; // 3.0 - 5.0
}
private async searchArticleIds(query: string, filters: any, pagination: any): Promise<string[]> {
await new Promise(resolve => setTimeout(resolve, 200));
const { limit } = pagination;
return Array.from({ length: Math.min(limit, 5) }, (_, i) => `search_result_${i + 1}`);
}
private async getSearchResultCount(query: string, filters: any): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 100));
return 27; // Mock total count
}
}
// 🚀 Usage example
const demonstrateCMS = async () => {
const cms = new ArticleManager();
try {
// 📝 Create article
const newArticle = await cms.createArticle({
title: 'Mastering Async/Await in TypeScript: A Complete Guide',
content: 'This comprehensive guide covers everything you need to know about async/await patterns in TypeScript. We will explore modern asynchronous programming techniques, error handling strategies, and performance optimization patterns that will help you write better, more maintainable code.',
authorId: 'author_1',
categoryId: 'typescript',
tags: ['typescript', 'async', 'await', 'programming', 'tutorial'],
status: 'draft'
});
console.log('📝 Article created:', newArticle.id);
// 📊 Load with details
const articleDetails = await cms.loadArticleWithDetails(newArticle.id);
console.log('📊 Article loaded with details:', articleDetails.author.name);
// 🌐 Publish article
const publishResult = await cms.publishArticle(newArticle.id);
console.log('🌐 Publishing result:', publishResult.success, `SEO Score: ${publishResult.seoScore}`);
// 🔍 Search articles
const searchResults = await cms.searchArticles('typescript', {
categoryId: 'typescript'
});
console.log('🔍 Search results:', searchResults.articles.length);
} catch (error) {
console.error('💥 CMS operation failed:', error);
}
};
🏁 Conclusion
Congratulations! 🎉 You’ve mastered the art of async/await in TypeScript. You now have the skills to:
- ✅ Write clean async code that reads like synchronous code
- 🚨 Handle errors gracefully with sophisticated try/catch patterns
- 🚀 Combine async/await with parallel operations for optimal performance
- 🌊 Use advanced patterns like async generators and controlled concurrency
- 🏗️ Build complex applications with modern async architectures
You’ve learned to write async code that’s not just functional, but elegantly crafted and maintainable. Keep practicing these patterns, and you’ll be the async programming master your team needs! 🏆
🔗 Next Steps
Ready to level up further? Check out these advanced topics:
- 🌊 Async Iterators and Generators for streaming data processing
- 🎯 RxJS Observables for reactive programming patterns
- 🏭 Stream Processing with Node.js streams and TypeScript
- 🔄 Event-Driven Architecture with async event handlers
- 📡 GraphQL Resolvers with async data fetching