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!