HTML & JS Topic

Asynchronous JavaScript

Why Asynchronous?

The Problem: Blocking Operations

javascript

// Synchronous (blocks execution)
console.log('Start');

// This blocks for 3 seconds - everything waits!
const data = simulateBlockingAPI(3000);
console.log('Data:', data);

console.log('End');

function simulateBlockingAPI(ms) {
    const start = Date.now();
    while (Date.now() - start < ms) {
        // Blocking loop
    }
    return 'API Data';
}
// Output: Start → (3 second delay) → Data: API Data → End

The Solution: Asynchronous

javascript

// Asynchronous (non-blocking)
console.log('Start');

// This doesn't block - execution continues
simulateAsyncAPI(3000, function(data) {
    console.log('Data:', data);
});

console.log('End');

function simulateAsyncAPI(ms, callback) {
    setTimeout(() => {
        callback('API Data');
    }, ms);
}
// Output: Start → End → (3 seconds later) → Data: API Data

Callbacks

Basic Callback Pattern:

javascript

// Synchronous callback (immediate execution)
function greet(name, callback) {
    console.log(`Hello, ${name}!`);
    callback();
}

greet('John', function() {
    console.log('Callback executed');
});
// Output: Hello, John! → Callback executed

// Asynchronous callback (delayed execution)
function fetchData(callback) {
    setTimeout(() => {
        callback({ data: 'Some data' });
    }, 1000);
}

fetchData(function(result) {
    console.log('Data received:', result);
});
console.log('Fetching data...');
// Output: Fetching data... → (1 second later) → Data received: {data: 'Some data'}
Real-world Callback Examples:
javascript

// File reading callback (Node.js style)
function readFile(filename, callback) {
    console.log(`Reading ${filename}...`);
    setTimeout(() => {
        const error = Math.random() > 0.7 ? new Error('File not found') : null;
        const data = error ? null : `Content of ${filename}`;
        callback(error, data);
    }, 1000);
}

readFile('document.txt', function(error, data) {
    if (error) {
        console.error('Error:', error.message);
    } else {
        console.log('File content:', data);
    }
});

// AJAX with callback (traditional)
function getJSON(url, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = function() {
        if (xhr.status === 200) {
            callback(null, JSON.parse(xhr.responseText));
        } else {
            callback(new Error(`HTTP ${xhr.status}`));
        }
    };
    xhr.onerror = function() {
        callback(new Error('Network error'));
    };
    xhr.send();
}

// Usage
getJSON('https://api.example.com/data', function(error, data) {
    if (error) {
        console.error('Failed:', error);
    } else {
        console.log('Data:', data);
    }
});

// Multiple async operations with callbacks
function getUserData(userId, callback) {
    setTimeout(() => {
        console.log('Got user data');
        callback(null, { id: userId, name: 'John' });
    }, 500);
}

function getUserPosts(userId, callback) {
    setTimeout(() => {
        console.log('Got user posts');
        callback(null, [{ id: 1, title: 'Post 1' }]);
    }, 500);
}

function getUserComments(userId, callback) {
    setTimeout(() => {
        console.log('Got user comments');
        callback(null, [{ id: 1, text: 'Great post!' }]);
    }, 500);
}

// Nested callbacks (callback hell!)
getUserData(123, function(error, user) {
    if (error) return console.error(error);
    
    getUserPosts(user.id, function(error, posts) {
        if (error) return console.error(error);
        
        getUserComments(user.id, function(error, comments) {
            if (error) return console.error(error);
            
            console.log('Complete user profile:', {
                user,
                posts,
                comments
            });
        });
    });
});

Callback Hell and Solutions:

javascript

// CALLBACK HELL (Pyramid of Doom)
asyncOperation1(function(error, result1) {
    if (error) handleError(error);
    else {
        asyncOperation2(result1, function(error, result2) {
            if (error) handleError(error);
            else {
                asyncOperation3(result2, function(error, result3) {
                    if (error) handleError(error);
                    else {
                        asyncOperation4(result3, function(error, result4) {
                            // ... and so on
                        });
                    }
                });
            }
        });
    }
});

// SOLUTION 1: Named functions
function handleResult1(error, result1) {
    if (error) handleError(error);
    else asyncOperation2(result1, handleResult2);
}

function handleResult2(error, result2) {
    if (error) handleError(error);
    else asyncOperation3(result2, handleResult3);
}

function handleResult3(error, result3) {
    if (error) handleError(error);
    else asyncOperation4(result3, handleResult4);
}

asyncOperation1(handleResult1);

// SOLUTION 2: Use Promises or async/await (better!)

Promises

Promise Basics:

javascript

// Creating a Promise
const promise = new Promise((resolve, reject) => {
    // Async operation
    setTimeout(() => {
        const success = Math.random() > 0.3;
        
        if (success) {
            resolve({ data: 'Success!' }); // Fulfilled
        } else {
            reject(new Error('Failed!'));   // Rejected
        }
    }, 1000);
});

// Consuming a Promise
promise
    .then(result => {
        console.log('Success:', result);
        return result.data; // Pass to next .then
    })
    .catch(error => {
        console.error('Error:', error.message);
        return 'Default value'; // Recover from error
    })
    .finally(() => {
        console.log('Always runs');
    });

Promise States:

javascript

// 1. Pending
const pending = new Promise((resolve, reject) => {
    // Not resolved or rejected yet
});

// 2. Fulfilled
const fulfilled = Promise.resolve('Immediate value');
// or
new Promise(resolve => resolve('Value'));

// 3. Rejected
const rejected = Promise.reject(new Error('Oops'));
// or
new Promise((resolve, reject) => reject(new Error('Oops')));

// Check state (note: these are not standard methods)
console.log(pending);  // Promise {<pending>}
console.log(fulfilled); // Promise {<fulfilled>: "Immediate value"}

Real Promise Examples:

javascript

// Simulating API calls with Promises
function fetchUser(userId) {
    return new Promise((resolve, reject) => {
        console.log(`Fetching user ${userId}...`);
        setTimeout(() => {
            if (userId > 0 && userId < 100) {
                resolve({ id: userId, name: `User${userId}` });
            } else {
                reject(new Error('User not found'));
            }
        }, 1000);
    });
}

function fetchPosts(userId) {
    return new Promise((resolve, reject) => {
        console.log(`Fetching posts for user ${userId}...`);
        setTimeout(() => {
            resolve([{ id: 1, userId, title: 'Post 1' }]);
        }, 800);
    });
}

function fetchComments(postId) {
    return new Promise((resolve, reject) => {
        console.log(`Fetching comments for post ${postId}...`);
        setTimeout(() => {
            resolve([{ id: 1, postId, text: 'Nice post!' }]);
        }, 600);
    });
}

// Chaining Promises
fetchUser(1)
    .then(user => {
        console.log('Got user:', user.name);
        return fetchPosts(user.id);
    })
    .then(posts => {
        console.log('Got posts:', posts.length);
        return fetchComments(posts[0].id);
    })
    .then(comments => {
        console.log('Got comments:', comments.length);
    })
    .catch(error => {
        console.error('Error:', error.message);
    });

// Promise.all - Wait for all promises
Promise.all([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
])
.then(users => {
    console.log('All users:', users);
})
.catch(error => {
    console.error('One request failed:', error);
});

// Promise.allSettled - Wait for all, get results regardless
Promise.allSettled([
    fetchUser(1),
    fetchUser(999), // This will fail
    fetchUser(3)
])
.then(results => {
    results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
            console.log(`User ${index + 1}:`, result.value.name);
        } else {
            console.log(`User ${index + 1} failed:`, result.reason.message);
        }
    });
});

// Promise.race - First to finish wins
Promise.race([
    fetchUser(1),
    new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), 500)
    )
])
.then(user => {
    console.log('Fastest:', user.name);
})
.catch(error => {
    console.error('Timeout or error:', error.message);
});

// Promise.any - First to succeed wins (ignore failures)
Promise.any([
    Promise.reject(new Error('Failed 1')),
    Promise.reject(new Error('Failed 2')),
    fetchUser(1)
])
.then(user => {
    console.log('First successful:', user.name);
})
.catch(error => {
    console.error('All promises rejected:', error.errors);
});

Creating Custom Promise-based Functions:

javascript

// Convert callback-based to Promise-based
function readFilePromise(filename) {
    return new Promise((resolve, reject) => {
        readFile(filename, (error, data) => {
            if (error) reject(error);
            else resolve(data);
        });
    });
}

// Usage
readFilePromise('document.txt')
    .then(data => console.log(data))
    .catch(error => console.error(error));

// Promisify any callback function
function promisify(fn) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            fn(...args, (error, result) => {
                if (error) reject(error);
                else resolve(result);
            });
        });
    };
}

const readFileAsync = promisify(readFile);
const getJSONAsync = promisify(getJSON);

// Fetch with retry logic
function fetchWithRetry(url, retries = 3) {
    return new Promise((resolve, reject) => {
        function attempt() {
            fetch(url)
                .then(response => {
                    if (!response.ok) throw new Error(`HTTP ${response.status}`);
                    return response.json();
                })
                .then(resolve)
                .catch(error => {
                    if (retries > 0) {
                        console.log(`Retrying... (${retries} left)`);
                        retries--;
                        setTimeout(attempt, 1000);
                    } else {
                        reject(error);
                    }
                });
        }
        attempt();
    });
}

// Sequential vs Parallel execution
async function sequentialOperations() {
    const user = await fetchUser(1);      // Wait for this
    const posts = await fetchPosts(user.id); // Then this
    const comments = await fetchComments(posts[0].id); // Then this
    // Total time: sum of all delays
}

async function parallelOperations() {
    const [user, posts, comments] = await Promise.all([
        fetchUser(1),
        fetchPosts(1),
        fetchComments(1)
    ]);
    // Total time: time of longest operation
}

Async/Await

Basic Async/Await:

javascript

// Async function always returns a Promise
async function getData() {
    return 'Some data';
}

// Equivalent to:
function getData() {
    return Promise.resolve('Some data');
}

// Using await
async function fetchData() {
    try {
        console.log('Fetching data...');
        
        // Pause execution until promise resolves
        const data = await fetchUser(1);
        console.log('User:', data.name);
        
        const posts = await fetchPosts(data.id);
        console.log('Posts:', posts.length);
        
        return { user: data, posts };
        
    } catch (error) {
        console.error('Error fetching:', error.message);
        throw error; // Re-throw if needed
    } finally {
        console.log('Fetch attempt completed');
    }
}

// Call async function
fetchData()
    .then(result => console.log('Complete:', result))
    .catch(error => console.error('Failed:', error));

Async/Await Patterns:

javascript

// 1. Sequential execution
async function processSequentially() {
    const result1 = await asyncOperation1();
    const result2 = await asyncOperation2(result1);
    const result3 = await asyncOperation3(result2);
    return result3;
}

// 2. Parallel execution
async function processInParallel() {
    const [result1, result2, result3] = await Promise.all([
        asyncOperation1(),
        asyncOperation2(),
        asyncOperation3()
    ]);
    return { result1, result2, result3 };
}

// 3. Loop with async/await
async function processItems(items) {
    const results = [];
    for (const item of items) {
        const result = await processItem(item);
        results.push(result);
    }
    return results;
}

// 4. Concurrent processing with limit
async function processWithConcurrency(items, concurrency = 3) {
    const results = [];
    
    for (let i = 0; i < items.length; i += concurrency) {
        const batch = items.slice(i, i + concurrency);
        const batchResults = await Promise.all(
            batch.map(item => processItem(item))
        );
        results.push(...batchResults);
    }
    
    return results;
}

// 5. Async error handling patterns
async function safeOperation() {
    try {
        return await riskyOperation();
    } catch (error) {
        console.error('Operation failed:', error);
        return null;
    }
}

// 6. Conditional async execution
async function getUserProfile(userId, includePosts = true) {
    const user = await fetchUser(userId);
    
    let posts = [];
    if (includePosts) {
        posts = await fetchPosts(userId);
    }
    
    return { user, posts };
}

// 7. Timeout wrapper
async function withTimeout(promise, ms) {
    const timeout = new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), ms)
    );
    
    return Promise.race([promise, timeout]);
}

// Usage
try {
    const result = await withTimeout(fetchUser(1), 5000);
} catch (error) {
    if (error.message === 'Timeout') {
        console.log('Request took too long');
    }
}

Real Async/Await Examples:

javascript

// E-commerce checkout flow
async function checkout(orderId, paymentDetails) {
    try {
        console.log('Starting checkout...');
        
        // 1. Validate order
        const order = await validateOrder(orderId);
        if (!order.valid) {
            throw new Error('Invalid order');
        }
        
        // 2. Process payment (parallel with inventory check)
        const [paymentResult, inventoryCheck] = await Promise.all([
            processPayment(paymentDetails, order.total),
            checkInventory(order.items)
        ]);
        
        if (!paymentResult.success) {
            throw new Error('Payment failed');
        }
        
        if (!inventoryCheck.allAvailable) {
            throw new Error('Some items out of stock');
        }
        
        // 3. Create shipping
        const shipping = await createShippingLabel(order);
        
        // 4. Update order and notify
        await Promise.all([
            updateOrderStatus(orderId, 'completed'),
            sendConfirmationEmail(order.customerEmail, orderId),
            notifyWarehouse(orderId)
        ]);
        
        console.log('Checkout completed successfully!');
        return { success: true, orderId, trackingNumber: shipping.trackingNumber };
        
    } catch (error) {
        console.error('Checkout failed:', error.message);
        
        // Rollback or cleanup
        await rollbackOrder(orderId);
        await sendFailureEmail(order.customerEmail, error.message);
        
        return { success: false, error: error.message };
    }
}

// Data fetching with pagination
async function fetchAllPages(endpoint, pageSize = 50) {
    let allData = [];
    let page = 1;
    let hasMore = true;
    
    while (hasMore) {
        console.log(`Fetching page ${page}...`);
        
        try {
            const response = await fetch(`${endpoint}?page=${page}&size=${pageSize}`);
            const data = await response.json();
            
            if (data.items && data.items.length > 0) {
                allData.push(...data.items);
                page++;
                
                // Check if there are more pages
                hasMore = data.hasMore || data.items.length === pageSize;
            } else {
                hasMore = false;
            }
            
            // Add small delay to avoid rate limiting
            if (hasMore) {
                await new Promise(resolve => setTimeout(resolve, 100));
            }
            
        } catch (error) {
            console.error(`Error fetching page ${page}:`, error);
            // Optional: implement retry logic here
            break;
        }
    }
    
    console.log(`Fetched ${allData.length} items from ${page - 1} pages`);
    return allData;
}

// Upload with progress
async function uploadFile(file, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.onprogress = event => {
            if (event.lengthComputable) {
                const percent = Math.round((event.loaded / event.total) * 100);
                onProgress(percent);
            }
        };
        
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`Upload failed: ${xhr.status}`));
            }
        };
        
        xhr.onerror = () => reject(new Error('Network error'));
        
        const formData = new FormData();
        formData.append('file', file);
        
        xhr.open('POST', '/api/upload');
        xhr.send(formData);
    });
}

// Usage
async function handleFileUpload(file) {
    try {
        const result = await uploadFile(file, (percent) => {
            console.log(`Upload progress: ${percent}%`);
            // Update progress bar UI
        });
        console.log('Upload successful:', result);
    } catch (error) {
        console.error('Upload failed:', error);
    }
}

Error Handling

Error Handling with Promises:

javascript

// Basic error handling
fetchUser(999)
    .then(user => console.log(user))
    .catch(error => console.error('Error:', error.message))
    .finally(() => console.log('Cleanup'));

// Error propagation in chains
fetchUser(1)
    .then(user => {
        // This might throw
        if (!user.active) {
            throw new Error('User inactive');
        }
        return fetchPosts(user.id);
    })
    .then(posts => {
        console.log('Posts:', posts);
    })
    .catch(error => {
        // Catches errors from any point in chain
        console.error('Chain failed:', error.message);
    });

// Multiple catch handlers
fetchUser(1)
    .then(user => {
        if (!user.active) {
            throw new Error('User inactive');
        }
        return user;
    })
    .catch(error => {
        if (error.message === 'User inactive') {
            console.log('User is inactive, using default');
            return getDefaultUser();
        }
        throw error; // Re-throw for other errors
    })
    .then(user => {
        console.log('Processing user:', user.name);
    })
    .catch(error => {
        console.error('Fatal error:', error);
    });

// Handle errors without stopping chain
fetchUser(1)
    .then(user => {
        return fetchPosts(user.id)
            .catch(error => {
                console.log('Could not fetch posts, using empty array');
                return [];
            });
    })
    .then(posts => {
        console.log('Processing posts:', posts);
    });

Error Handling with Async/Await:

javascript

// Basic try-catch
async function getUserProfile(userId) {
    try {
        const user = await fetchUser(userId);
        const posts = await fetchPosts(userId);
        return { user, posts };
    } catch (error) {
        console.error('Failed to get profile:', error);
        // Return default or re-throw
        throw error;
    }
}

// Handling specific error types
async function processData() {
    try {
        const data = await fetchData();
        return await transformData(data);
    } catch (error) {
        if (error instanceof TypeError) {
            console.error('Type error:', error);
            return null;
        } else if (error instanceof NetworkError) {
            console.error('Network error:', error);
            throw new RetryableError(error.message);
        } else {
            throw error;
        }
    }
}

// Multiple async operations with individual error handling
async function getCompleteProfile(userId) {
    const userPromise = fetchUser(userId)
        .catch(() => ({ id: userId, name: 'Unknown User' }));
    
    const postsPromise = fetchPosts(userId)
        .catch(() => []);
    
    const commentsPromise = fetchComments(userId)
        .catch(() => []);
    
    const [user, posts, comments] = await Promise.all([
        userPromise,
        postsPromise,
        commentsPromise
    ]);
    
    return { user, posts, comments };
}

// Retry pattern with exponential backoff
async function fetchWithRetry(url, maxRetries = 3) {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return await response.json();
        } catch (error) {
            lastError = error;
            
            if (attempt < maxRetries) {
                const delay = Math.pow(2, attempt) * 100; // Exponential backoff
                console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
                await new Promise(resolve => setTimeout(resolve, delay));
            }
        }
    }
    
    throw lastError;
}

// Fallback pattern
async function getDataWithFallback(primarySource, fallbackSource) {
    try {
        return await fetch(primarySource).then(r => r.json());
    } catch (error) {
        console.log('Primary source failed, trying fallback...');
        try {
            return await fetch(fallbackSource).then(r => r.json());
        } catch (fallbackError) {
            throw new Error(
                `Both sources failed: ${error.message}, ${fallbackError.message}`
            );
        }
    }
}

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?