Prerequisites
- Understanding of Promise fundamentals ๐
- Basic async/await syntax knowledge โก
- TypeScript function types experience ๐ป
What you'll learn
- Master async function return types and behavior ๐ฏ
- Understand async function execution patterns ๐๏ธ
- Handle async function errors effectively ๐
- Build efficient async applications with TypeScript โจ
๐ฏ Introduction
Welcome to the fascinating world of async functions! ๐ฎ While Promise types tell us what weโre working with, async functions show us HOW asynchronous magic happens. Theyโre the elegant wrapper that makes asynchronous code look and feel synchronous!
Think of async functions as magical time machines โฐ - they let you write code that โtravels to the futureโ to get values, but your code reads like it happens instantly. No more callback pyramids or complex Promise chains!
By the end of this tutorial, youโll understand exactly how async functions work under the hood, their return type behavior, and how to use them to build clean, maintainable asynchronous applications. Letโs unlock the secrets! ๐
๐ Understanding Async Functions
๐ค What Are Async Functions?
Async functions are like magical translators ๐. They take your synchronous-looking code and automatically convert it into Promise-based asynchronous operations. Itโs syntactic sugar that makes async code readable and maintainable!
// ๐ฎ The magic transformation
async function fetchUserData(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const userData = await response.json();
return userData; // โจ Automatically wrapped in Promise.resolve()
}
// ๐ญ What it's really doing behind the scenes:
function fetchUserDataExplicit(id: string): Promise<User> {
return fetch(`/api/users/${id}`)
.then(response => response.json())
.then(userData => userData);
}
๐ก Key Characteristics
- ๐ Automatic Promise Wrapping: Return values are automatically wrapped in
Promise.resolve() - โก Await Integration: Can use
awaitto pause execution until Promises resolve - ๐ฏ Type Safety: TypeScript provides excellent inference for async return types
- ๐ก๏ธ Error Handling: Thrown errors automatically become rejected Promises
// ๐จ TypeScript's type inference magic
async function getString(): Promise<string> {
return "hello"; // TypeScript knows this becomes Promise<string>
}
async function getNumber(): Promise<number> {
if (Math.random() > 0.5) {
return 42; // Auto-wrapped in Promise.resolve(42)
}
throw new Error("Bad luck! ๐ฒ"); // Auto-wrapped in Promise.reject()
}
๐ Async Functions vs Regular Functions
// ๐โโ๏ธ Regular function - synchronous
function calculateSync(a: number, b: number): number {
return a + b; // Immediate return
}
// ๐ฎ Async function - always returns Promise
async function calculateAsync(a: number, b: number): Promise<number> {
return a + b; // Wrapped in Promise.resolve()
}
// ๐ Mixed async/sync operations
async function smartCalculation(a: number, b: number): Promise<number> {
const intermediate = calculateSync(a, b); // Sync call
const factor = await getMultiplierFromAPI(); // Async call
return intermediate * factor; // Final async result
}
๐ง Return Type Behavior
๐ Understanding Promise Return Types
The magic of async functions lies in their automatic Promise wrapping:
// ๐ฏ Return type examples
async function returnString(): Promise<string> {
return "hello"; // string becomes Promise<string>
}
async function returnNumber(): Promise<number> {
return 42; // number becomes Promise<number>
}
async function returnObject(): Promise<{ name: string; age: number }> {
return { name: "Alice", age: 30 }; // object becomes Promise<object>
}
// ๐ฅ Even if you explicitly return a Promise, TypeScript handles it!
async function returnPromise(): Promise<string> {
return Promise.resolve("explicit promise"); // Promise<string> stays Promise<string>
}
๐จ Complex Return Type Scenarios
// ๐๏ธ E-commerce order processing system
interface OrderItem {
productId: string;
quantity: number;
price: number;
}
interface Order {
id: string;
items: OrderItem[];
customerEmail: string;
total: number;
status: 'pending' | 'processing' | 'completed' | 'failed';
}
class OrderProcessor {
// ๐ Calculate order total (could involve async price lookups)
async calculateTotal(items: OrderItem[]): Promise<number> {
let total = 0;
for (const item of items) {
// ๐ฐ Each price lookup might be async (database/API call)
const currentPrice = await this.getCurrentPrice(item.productId);
total += currentPrice * item.quantity;
}
return total; // number automatically becomes Promise<number>
}
// ๐ช Get current price from external service
private async getCurrentPrice(productId: string): Promise<number> {
// ๐ Simulate API call
const response = await fetch(`/api/products/${productId}/price`);
const data = await response.json();
return data.price; // TypeScript infers the return type
}
// ๐ฆ Process entire order - returns different types based on success/failure
async processOrder(orderData: Omit<Order, 'id' | 'total' | 'status'>): Promise<Order> {
try {
// ๐ฏ Generate order ID
const orderId = await this.generateOrderId();
// ๐ฐ Calculate final total
const total = await this.calculateTotal(orderData.items);
// ๐ง Validate customer
await this.validateCustomer(orderData.customerEmail);
// ๐๏ธ Create final order
const order: Order = {
id: orderId,
...orderData,
total,
status: 'processing'
};
// ๐พ Save to database
await this.saveOrder(order);
return order; // Order becomes Promise<Order>
} catch (error) {
console.error('Order processing failed:', error);
throw new Error(`Failed to process order: ${error.message}`);
}
}
// ๐ข Generate unique order ID
private async generateOrderId(): Promise<string> {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
return `ORDER_${timestamp}_${random}`;
}
// โ
Validate customer exists
private async validateCustomer(email: string): Promise<void> {
const customer = await this.findCustomerByEmail(email);
if (!customer) {
throw new Error(`Customer not found: ${email}`);
}
// โก Notice: void return becomes Promise<void>
}
// ๐ Find customer in database
private async findCustomerByEmail(email: string): Promise<{ id: string; email: string } | null> {
// ๐ Simulate database lookup
const response = await fetch(`/api/customers?email=${email}`);
if (response.status === 404) {
return null; // null becomes Promise<null>
}
return response.json(); // object becomes Promise<object>
}
// ๐พ Save order to database
private async saveOrder(order: Order): Promise<void> {
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order)
});
// ๐ฏ No explicit return - TypeScript infers Promise<void>
}
}
๐ก Execution Patterns and Behavior
๐ฎ Sequential vs Parallel Execution
Understanding when operations run sequentially vs in parallel is crucial:
// ๐๏ธ File processing service
class FileProcessor {
// ๐ Sequential execution - each await blocks the next
async processFilesSequentially(filePaths: string[]): Promise<string[]> {
const results: string[] = [];
for (const filePath of filePaths) {
console.log(`๐ Processing ${filePath}...`);
const content = await this.readFile(filePath); // Wait for this
const processed = await this.processContent(content); // Then wait for this
const saved = await this.saveProcessedFile(processed); // Then wait for this
results.push(saved);
}
return results; // All files processed one by one
}
// ๐ Parallel execution - start all operations at once
async processFilesInParallel(filePaths: string[]): Promise<string[]> {
// ๐ฏ Start all read operations immediately
const readPromises = filePaths.map(filePath => this.readFile(filePath));
// โก Wait for all reads to complete
const contents = await Promise.all(readPromises);
// ๐ Process all contents in parallel
const processPromises = contents.map(content => this.processContent(content));
const processed = await Promise.all(processPromises);
// ๐พ Save all processed files in parallel
const savePromises = processed.map(content => this.saveProcessedFile(content));
const results = await Promise.all(savePromises);
return results; // Much faster! ๐โโ๏ธ
}
// ๐จ Mixed pattern - some sequential, some parallel
async processFilesWithValidation(filePaths: string[]): Promise<string[]> {
// ๐ First, validate all files exist (parallel)
console.log('๐ Validating files...');
const validationPromises = filePaths.map(path => this.validateFile(path));
await Promise.all(validationPromises);
const results: string[] = [];
// ๐ Then process in batches to avoid overwhelming the system
const batchSize = 3;
for (let i = 0; i < filePaths.length; i += batchSize) {
const batch = filePaths.slice(i, i + batchSize);
console.log(`๐ฆ Processing batch ${Math.floor(i / batchSize) + 1}...`);
// ๐ Process this batch in parallel
const batchPromises = batch.map(async (filePath) => {
const content = await this.readFile(filePath);
const processed = await this.processContent(content);
return this.saveProcessedFile(processed);
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
// ๐๏ธ Helper methods
private async readFile(filePath: string): Promise<string> {
console.log(`๐ Reading ${filePath}`);
await this.delay(100); // Simulate file I/O
return `Content of ${filePath}`;
}
private async processContent(content: string): Promise<string> {
console.log(`โ๏ธ Processing content...`);
await this.delay(200); // Simulate processing time
return `Processed: ${content}`;
}
private async saveProcessedFile(content: string): Promise<string> {
console.log(`๐พ Saving processed file...`);
await this.delay(150); // Simulate save operation
return `Saved: ${content}`;
}
private async validateFile(filePath: string): Promise<void> {
console.log(`โ
Validating ${filePath}`);
await this.delay(50); // Simulate validation
if (Math.random() < 0.1) { // 10% chance of invalid file
throw new Error(`Invalid file: ${filePath}`);
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Usage example
async function demonstrateExecutionPatterns(): Promise<void> {
const processor = new FileProcessor();
const files = ['file1.txt', 'file2.txt', 'file3.txt', 'file4.txt'];
console.log('๐ Sequential processing:');
const start1 = Date.now();
await processor.processFilesSequentially(files);
console.log(`โฑ๏ธ Sequential took: ${Date.now() - start1}ms\n`);
console.log('๐ Parallel processing:');
const start2 = Date.now();
await processor.processFilesInParallel(files);
console.log(`โฑ๏ธ Parallel took: ${Date.now() - start2}ms\n`);
console.log('๐จ Mixed pattern:');
const start3 = Date.now();
await processor.processFilesWithValidation(files);
console.log(`โฑ๏ธ Mixed took: ${Date.now() - start3}ms`);
}
๐ฏ Async Function Context and This Binding
// ๐๏ธ Understanding 'this' in async methods
class DataService {
private apiUrl = 'https://api.example.com';
private headers = { 'Authorization': 'Bearer token123' };
// โ
Async method - 'this' bound correctly
async fetchData(endpoint: string): Promise<any> {
const response = await fetch(`${this.apiUrl}/${endpoint}`, {
headers: this.headers // โ
'this' refers to the class instance
});
return response.json();
}
// ๐ฏ Arrow function async method - always bound
fetchDataArrow = async (endpoint: string): Promise<any> => {
const response = await fetch(`${this.apiUrl}/${endpoint}`, {
headers: this.headers // โ
'this' is lexically bound
});
return response.json();
}
// ๐ Method that returns async function
createFetcher(): (endpoint: string) => Promise<any> {
return async (endpoint: string): Promise<any> => {
const response = await fetch(`${this.apiUrl}/${endpoint}`, {
headers: this.headers // โ
'this' captured from outer scope
});
return response.json();
};
}
}
๐ Advanced Async Patterns
๐งโโ๏ธ Async Generators and Iteration
// ๐ Data streaming with async generators
class DataStream {
// ๐ Async generator for paginated data
async* fetchAllPages(baseUrl: string): AsyncGenerator<any[], void, unknown> {
let page = 1;
let hasMore = true;
while (hasMore) {
console.log(`๐ Fetching page ${page}...`);
const response = await fetch(`${baseUrl}?page=${page}&limit=10`);
const data = await response.json();
// ๐ Yield current page data
yield data.items;
// ๐ Check if there are more pages
hasMore = data.hasMore;
page++;
// ๐ด Rate limiting - wait between requests
await this.delay(100);
}
console.log('โ
All pages fetched!');
}
// ๐๏ธ Process streamed data
async processStreamedData(baseUrl: string): Promise<void> {
// ๐ Use for-await-of to consume async generator
for await (const pageData of this.fetchAllPages(baseUrl)) {
console.log(`๐ Processing ${pageData.length} items from current page`);
// ๐ฏ Process each item in the page
for (const item of pageData) {
await this.processItem(item);
}
}
}
private async processItem(item: any): Promise<void> {
console.log(`โ๏ธ Processing item: ${item.id}`);
await this.delay(10); // Simulate processing
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
๐๏ธ Async Constructor Patterns
// ๐ฏ Handling async initialization
class DatabaseConnection {
private connection: any = null;
private initialized = false;
private constructor(private connectionString: string) {}
// ๐ญ Static factory method for async creation
static async create(connectionString: string): Promise<DatabaseConnection> {
const instance = new DatabaseConnection(connectionString);
await instance.initialize();
return instance;
}
private async initialize(): Promise<void> {
console.log('๐ Connecting to database...');
// ๐ Simulate async connection establishment
this.connection = await this.establishConnection();
this.initialized = true;
console.log('โ
Database connected successfully!');
}
async query(sql: string): Promise<any[]> {
if (!this.initialized) {
throw new Error('โ Database not initialized. Use DatabaseConnection.create()');
}
console.log(`๐ Executing query: ${sql}`);
// ๐ฏ Simulate query execution
await this.delay(50);
return [{ id: 1, name: 'Mock Data' }];
}
private async establishConnection(): Promise<any> {
await this.delay(200); // Simulate connection time
return { connected: true, connectionId: Date.now() };
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Usage pattern
async function demonstrateDatabasePattern(): Promise<void> {
try {
// โ
Correct way - use static factory method
const db = await DatabaseConnection.create('postgresql://localhost:5432/mydb');
const results = await db.query('SELECT * FROM users');
console.log('Query results:', results);
} catch (error) {
console.error('Database error:', error);
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Floating Promises
// โ Dangerous - unhandled promise rejection!
async function dangerousCode(): Promise<void> {
someAsyncOperation(); // ๐ฅ Floating promise - no await or .catch()
console.log('This runs immediately!');
}
// โ
Safe - properly handled
async function safeCode(): Promise<void> {
try {
await someAsyncOperation(); // โ
Properly awaited
console.log('This runs after async operation!');
} catch (error) {
console.error('Handled error:', error);
}
}
// ๐ฏ Alternative - fire and forget with explicit error handling
async function fireAndForget(): Promise<void> {
someAsyncOperation().catch(error => {
console.error('Background operation failed:', error);
}); // โ
Explicitly ignore result but handle errors
console.log('This runs immediately!');
}
async function someAsyncOperation(): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 100));
if (Math.random() < 0.3) {
throw new Error('Random failure!');
}
}
๐คฏ Pitfall 2: Serial vs Parallel Confusion
// โ Accidentally serial execution
async function slowVersion(urls: string[]): Promise<string[]> {
const results: string[] = [];
for (const url of urls) {
const response = await fetch(url); // ๐ Waits for each one
results.push(await response.text());
}
return results;
}
// โ
Intentionally parallel execution
async function fastVersion(urls: string[]): Promise<string[]> {
const promises = urls.map(async (url) => {
const response = await fetch(url);
return response.text();
});
return Promise.all(promises); // ๐ All fetch at once
}
// ๐ฏ Controlled parallelism
async function controlledVersion(urls: string[], concurrency = 3): Promise<string[]> {
const results: string[] = [];
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency);
const batchPromises = batch.map(async (url) => {
const response = await fetch(url);
return response.text();
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
๐ฅ Pitfall 3: Memory Leaks with Large Arrays
// โ Memory intensive - loads everything at once
async function memoryHungry(urls: string[]): Promise<void> {
const allData = await Promise.all(
urls.map(url => fetch(url).then(r => r.blob())) // ๐พ All files in memory
);
// ๐ Process all data
for (const data of allData) {
await processLargeFile(data);
}
}
// โ
Memory efficient - streaming approach
async function memoryFriendly(urls: string[]): Promise<void> {
for (const url of urls) {
const response = await fetch(url);
const data = await response.blob();
await processLargeFile(data); // ๐ Process one at a time
// ๐๏ธ Data can be garbage collected here
}
}
async function processLargeFile(data: Blob): Promise<void> {
console.log(`Processing ${data.size} bytes...`);
await new Promise(resolve => setTimeout(resolve, 100));
}
๐ ๏ธ Best Practices
๐ฏ Type-Safe Error Handling
// ๐ก๏ธ Type-safe result pattern
type Result<T, E = Error> = {
success: true;
data: T;
} | {
success: false;
error: E;
};
class ApiService {
// ๐ฏ Method that never throws - returns Result instead
async safeApiCall<T>(url: string): Promise<Result<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
return {
success: false,
error: new Error(`HTTP ${response.status}: ${response.statusText}`)
};
}
const data = await response.json();
return {
success: true,
data
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error')
};
}
}
// ๐ฎ Usage example
async handleApiCall(): Promise<void> {
const result = await this.safeApiCall<{ name: string; age: number }>('/api/user/123');
if (result.success) {
console.log(`User: ${result.data.name}, Age: ${result.data.age}`);
} else {
console.error('API call failed:', result.error.message);
}
}
}
๐จ Performance Monitoring
// ๐ Performance tracking for async operations
class PerformanceTracker {
private static measurements = new Map<string, number[]>();
static async measure<T>(
label: string,
operation: () => Promise<T>
): Promise<T> {
const start = performance.now();
try {
const result = await operation();
const duration = performance.now() - start;
// ๐ Store measurement
if (!this.measurements.has(label)) {
this.measurements.set(label, []);
}
this.measurements.get(label)!.push(duration);
console.log(`โฑ๏ธ ${label}: ${duration.toFixed(2)}ms`);
return result;
} catch (error) {
const duration = performance.now() - start;
console.log(`โ ${label} failed after ${duration.toFixed(2)}ms`);
throw error;
}
}
static getStats(label: string): { avg: number; min: number; max: number; count: number } | null {
const measurements = this.measurements.get(label);
if (!measurements || measurements.length === 0) return null;
return {
avg: measurements.reduce((a, b) => a + b, 0) / measurements.length,
min: Math.min(...measurements),
max: Math.max(...measurements),
count: measurements.length
};
}
}
// ๐ฎ Usage example
async function demonstratePerformanceTracking(): Promise<void> {
// ๐ Track different operations
await PerformanceTracker.measure('database-query', async () => {
await new Promise(resolve => setTimeout(resolve, 100));
return { rows: 10 };
});
await PerformanceTracker.measure('api-call', async () => {
await new Promise(resolve => setTimeout(resolve, 200));
return { status: 'success' };
});
// ๐ Get performance statistics
const dbStats = PerformanceTracker.getStats('database-query');
const apiStats = PerformanceTracker.getStats('api-call');
console.log('๐ Performance Summary:');
if (dbStats) console.log(`Database: avg ${dbStats.avg.toFixed(2)}ms`);
if (apiStats) console.log(`API: avg ${apiStats.avg.toFixed(2)}ms`);
}
๐งช Hands-On Exercise
๐ฏ Challenge: Build an Async Task Queue
Create a type-safe task queue system that processes async operations with concurrency control:
๐ Requirements:
- โ Generic task types with different priorities
- ๐ท๏ธ Concurrency limiting (max X tasks at once)
- ๐ค Task result tracking and error handling
- ๐ Retry logic for failed tasks
- ๐จ Progress reporting with events
๐ Bonus Points:
- Add task dependencies (task B runs after task A)
- Implement task cancellation
- Create metrics and monitoring
๐ก Solution
๐ Click to see solution
// ๐ฏ Type-safe async task queue system!
interface Task<T> {
id: string;
operation: () => Promise<T>;
priority: 'low' | 'medium' | 'high';
retries: number;
maxRetries: number;
dependencies?: string[];
}
interface TaskResult<T> {
taskId: string;
success: boolean;
data?: T;
error?: Error;
attempts: number;
duration: number;
}
class AsyncTaskQueue {
private pending = new Map<string, Task<any>>();
private running = new Map<string, Promise<any>>();
private completed = new Map<string, TaskResult<any>>();
private maxConcurrency: number;
constructor(maxConcurrency = 3) {
this.maxConcurrency = maxConcurrency;
}
// โ Add task to queue
async addTask<T>(task: Omit<Task<T>, 'id' | 'retries'>): Promise<string> {
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const fullTask: Task<T> = {
...task,
id: taskId,
retries: 0
};
this.pending.set(taskId, fullTask);
console.log(`๐ Added task ${taskId} (${task.priority} priority)`);
// ๐ Start processing if we have capacity
this.processQueue();
return taskId;
}
// ๐ Process the queue
private async processQueue(): Promise<void> {
if (this.running.size >= this.maxConcurrency) {
return; // At capacity
}
// ๐ฏ Find next eligible task
const nextTask = this.getNextTask();
if (!nextTask) {
return; // No eligible tasks
}
// ๐โโ๏ธ Start the task
this.startTask(nextTask);
// ๐ Try to start more tasks
this.processQueue();
}
// ๐ฏ Get next task based on priority and dependencies
private getNextTask(): Task<any> | null {
const eligibleTasks = Array.from(this.pending.values())
.filter(task => this.areDependenciesMet(task))
.sort((a, b) => this.getPriorityWeight(b.priority) - this.getPriorityWeight(a.priority));
return eligibleTasks[0] || null;
}
// โ
Check if task dependencies are met
private areDependenciesMet(task: Task<any>): boolean {
if (!task.dependencies) return true;
return task.dependencies.every(depId => {
const result = this.completed.get(depId);
return result && result.success;
});
}
// ๐ฏ Get priority weight for sorting
private getPriorityWeight(priority: 'low' | 'medium' | 'high'): number {
switch (priority) {
case 'high': return 3;
case 'medium': return 2;
case 'low': return 1;
}
}
// ๐ Start executing a task
private async startTask(task: Task<any>): Promise<void> {
this.pending.delete(task.id);
console.log(`๐ Starting task ${task.id} (attempt ${task.retries + 1})`);
const startTime = performance.now();
const taskPromise = this.executeTask(task, startTime);
this.running.set(task.id, taskPromise);
try {
await taskPromise;
} finally {
this.running.delete(task.id);
// ๐ Continue processing queue
this.processQueue();
}
}
// โ๏ธ Execute individual task with retry logic
private async executeTask(task: Task<any>, startTime: number): Promise<void> {
try {
task.retries++;
const result = await task.operation();
const duration = performance.now() - startTime;
// โ
Success!
const taskResult: TaskResult<any> = {
taskId: task.id,
success: true,
data: result,
attempts: task.retries,
duration
};
this.completed.set(task.id, taskResult);
console.log(`โ
Task ${task.id} completed in ${duration.toFixed(2)}ms`);
} catch (error) {
const duration = performance.now() - startTime;
if (task.retries < task.maxRetries) {
// ๐ Retry the task
console.log(`โ ๏ธ Task ${task.id} failed, retrying (${task.retries}/${task.maxRetries})`);
await this.delay(1000 * task.retries); // Exponential backoff
await this.executeTask(task, performance.now()); // New start time for retry
} else {
// โ Final failure
const taskResult: TaskResult<any> = {
taskId: task.id,
success: false,
error: error instanceof Error ? error : new Error('Unknown error'),
attempts: task.retries,
duration
};
this.completed.set(task.id, taskResult);
console.log(`โ Task ${task.id} failed permanently after ${task.retries} attempts`);
}
}
}
// ๐ Get queue status
getStatus(): {
pending: number;
running: number;
completed: number;
successful: number;
failed: number;
} {
const successful = Array.from(this.completed.values()).filter(r => r.success).length;
const failed = Array.from(this.completed.values()).filter(r => !r.success).length;
return {
pending: this.pending.size,
running: this.running.size,
completed: this.completed.size,
successful,
failed
};
}
// โณ Wait for specific task
async waitForTask<T>(taskId: string): Promise<TaskResult<T> | null> {
// Check if already completed
if (this.completed.has(taskId)) {
return this.completed.get(taskId) as TaskResult<T>;
}
// Check if running
if (this.running.has(taskId)) {
await this.running.get(taskId);
return this.completed.get(taskId) as TaskResult<T> || null;
}
// Not found
return null;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Demo usage
async function demonstrateTaskQueue(): Promise<void> {
const queue = new AsyncTaskQueue(2); // Max 2 concurrent tasks
// ๐ Add various tasks
const task1 = await queue.addTask({
operation: async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
return "Task 1 result";
},
priority: 'high',
maxRetries: 2
});
const task2 = await queue.addTask({
operation: async () => {
await new Promise(resolve => setTimeout(resolve, 500));
if (Math.random() < 0.7) throw new Error("Random failure");
return "Task 2 result";
},
priority: 'medium',
maxRetries: 3
});
const task3 = await queue.addTask({
operation: async () => {
await new Promise(resolve => setTimeout(resolve, 200));
return "Task 3 result";
},
priority: 'low',
maxRetries: 1,
dependencies: [task1] // Wait for task1 to complete
});
// ๐ Monitor progress
const monitor = setInterval(() => {
const status = queue.getStatus();
console.log(`๐ Status - Pending: ${status.pending}, Running: ${status.running}, Completed: ${status.completed} (${status.successful} successful, ${status.failed} failed)`);
}, 500);
// โณ Wait for specific task
const result1 = await queue.waitForTask(task1);
console.log('Task 1 result:', result1);
// โณ Wait a bit for all tasks to complete
setTimeout(() => {
clearInterval(monitor);
console.log('Final status:', queue.getStatus());
}, 5000);
}๐ Key Takeaways
Youโve mastered async functions! Hereโs what you can now do:
- โ Understand async function mechanics and automatic Promise wrapping ๐ช
- โ Design efficient execution patterns for performance ๐ก๏ธ
- โ Handle complex async scenarios with confidence ๐ฏ
- โ Avoid common async pitfalls like a pro ๐
- โ Build scalable async applications with TypeScript! ๐
Remember: Async functions are syntactic sugar that makes complex asynchronous operations look simple - but understanding what happens under the hood makes you a better developer! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered async functions and their behavior!
Hereโs what to do next:
- ๐ป Practice with the task queue exercise above
- ๐๏ธ Build a project using different async patterns
- ๐ Move on to our next tutorial: โAsync Generators: Yielding Promisesโ
- ๐ Share your async function knowledge with others!
Remember: Every async expert was once confused by Promises. Keep coding, keep learning, and embrace the asynchronous future! ๐
Happy async coding! ๐๐โจ