⏰
Asynchronous JavaScript
Master callbacks, promises, and async/await
🎯 Try This First: Async Step-by-Step
1
Start with a simple delay
See how JavaScript handles time delays:
console.log("Start")
setTimeout(() => {
console.log("This runs after 2 seconds")
}, 2000)
console.log("End")2
Create a Promise
Promises handle success and failure:
const myPromise = new Promise((resolve, reject) => {
const success = Math.random() > 0.5
setTimeout(() => {
if (success) {
resolve("Success! 🎉")
} else {
reject("Failed 😞")
}
}, 1000)
})
myPromise
.then(result => console.log(result))
.catch(error => console.log(error))3
Use async/await (cleaner syntax)
Modern way to handle promises:
async function testAsync() {
try {
console.log("Starting...")
const result = await myPromise
console.log("Got:", result)
} catch (error) {
console.log("Error:", error)
}
}
testAsync()4
Fetch real data from an API
Get data from the internet:
async function getUser() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1')
const user = await response.json()
console.log("User name:", user.name)
console.log("User email:", user.email)
} catch (error) {
console.log("Failed to fetch user:", error)
}
}
getUser()💡 Notice how async/await makes the code read like normal, step-by-step instructions!
Callbacks
Understanding Callbacks
// Basic Callback Example
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: "John Doe" };
callback(null, data); // First parameter is error, second is data
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error("Error:", error);
} else {
console.log("Data received:", data);
}
});
// Callback Hell Example
function getUserData(userId, callback) {
fetchUser(userId, (err, user) => {
if (err) return callback(err);
fetchUserPosts(user.id, (err, posts) => {
if (err) return callback(err);
fetchPostComments(posts[0].id, (err, comments) => {
if (err) return callback(err);
callback(null, { user, posts, comments });
});
});
});
}
// Error-First Callback Pattern
function readFile(filename, callback) {
// Simulate file reading
setTimeout(() => {
if (filename.endsWith('.txt')) {
callback(null, "File content here");
} else {
callback(new Error("Invalid file type"));
}
}, 500);
}⚠️ Callback Hell Problems
- • Deeply nested code that's hard to read
- • Difficult error handling
- • Hard to maintain and debug
- • Inversion of control issues
Promises
Promise Basics
// Creating a Promise
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: `User ${userId}` });
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
// Using Promises
fetchUserData(1)
.then(user => {
console.log("User:", user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log("Posts:", posts);
return fetchPostComments(posts[0].id);
})
.then(comments => {
console.log("Comments:", comments);
})
.catch(error => {
console.error("Error:", error);
})
.finally(() => {
console.log("Operation completed");
});
// Promise.all - Wait for all promises
const promises = [
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
];
Promise.all(promises)
.then(users => {
console.log("All users:", users);
})
.catch(error => {
console.error("One or more requests failed:", error);
});
// Promise.allSettled - Wait for all, regardless of outcome
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`User ${index + 1}:`, result.value);
} else {
console.error(`User ${index + 1} failed:`, result.reason);
}
});
});
// Promise.race - First to complete wins
Promise.race([
fetchUserData(1),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 2000)
)
])
.then(result => console.log("First result:", result))
.catch(error => console.error("Error or timeout:", error));Async/Await
Modern Async Syntax
// Basic Async/Await
async function getUserData(userId) {
try {
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error("Error fetching user data:", error);
throw error; // Re-throw if needed
}
}
// Using the async function
async function main() {
try {
const userData = await getUserData(1);
console.log("Complete user data:", userData);
} catch (error) {
console.error("Failed to get user data:", error);
}
}
// Parallel Execution with Async/Await
async function fetchMultipleUsers() {
try {
// Sequential (slower)
const user1 = await fetchUserData(1);
const user2 = await fetchUserData(2);
const user3 = await fetchUserData(3);
// Parallel (faster)
const [user1, user2, user3] = await Promise.all([
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
]);
return [user1, user2, user3];
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
}
// Async/Await with Error Handling
async function robustApiCall() {
const maxRetries = 3;
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
retries++;
if (retries === maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
}
}
}
// Async Generators
async function* fetchUsersPaginated() {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`/api/users?page=${page}`);
const data = await response.json();
yield data.users;
hasMore = data.hasMore;
page++;
}
}
// Using async generator
async function processAllUsers() {
for await (const userBatch of fetchUsersPaginated()) {
console.log(`Processing ${userBatch.length} users`);
// Process each batch
}
}Fetch API
Modern HTTP Requests
// Basic GET Request
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
return users;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
// POST Request with JSON
async function createUser(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to create user');
}
return await response.json();
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// File Upload
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData // Don't set Content-Type header for FormData
});
if (!response.ok) {
throw new Error('Upload failed');
}
return await response.json();
} catch (error) {
console.error('Upload error:', error);
throw error;
}
}
// Request with Timeout
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// Retry Logic
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
// Don't retry on client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
if (i === maxRetries) {
throw new Error(`Server error: ${response.status}`);
}
} catch (error) {
if (i === maxRetries) {
throw error;
}
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}Understanding the Event Loop
💡 How JavaScript Handles Async
Call Stack: Where function calls are executed
Web APIs: Browser APIs (setTimeout, fetch, DOM events)
Callback Queue: Where callbacks wait to be executed
Event Loop: Moves callbacks from queue to stack when stack is empty
Event Loop Example
console.log('1'); // Synchronous
setTimeout(() => {
console.log('2'); // Macro task
}, 0);
Promise.resolve().then(() => {
console.log('3'); // Micro task
});
console.log('4'); // Synchronous
// Output: 1, 4, 3, 2
// Explanation:
// 1. Synchronous code runs first (1, 4)
// 2. Micro tasks (Promises) run before macro tasks (setTimeout)
// 3. So Promise callback (3) runs before setTimeout callback (2)
// Micro tasks vs Macro tasks
setTimeout(() => console.log('macro 1'), 0);
setTimeout(() => console.log('macro 2'), 0);
Promise.resolve().then(() => console.log('micro 1'));
Promise.resolve().then(() => console.log('micro 2'));
// Output: micro 1, micro 2, macro 1, macro 2Async Best Practices
✅ Do
- • Use async/await for cleaner code
- • Handle errors with try/catch
- • Use Promise.all for parallel execution
- • Implement proper error boundaries
- • Use AbortController for cancellation
❌ Don't
- • Forget to handle promise rejections
- • Use async/await in loops unnecessarily
- • Mix callbacks and promises
- • Ignore network timeouts
- • Block the main thread with heavy operations