14. Asynchronous Programming
Synchronous vs Asynchronous
Synchronous Code
console.log("Start");
console.log("Middle");
console.log("End");
// Output: Start, Middle, End
Asynchronous Code
console.log("Start");
setTimeout(() => {
console.log("Async operation completed");
}, 1000);
console.log("End");
// Output: Start, End, Async operation completed
Callbacks
Basic Callback
function fetchData(callback) {
setTimeout(() => {
const data = { user: "John", age: 30 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log("Data received:", data);
});
Callback Hell
getUser(123, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log("Comments:", comments);
}, (error) => {
console.error("Error getting comments:", error);
});
}, (error) => {
console.error("Error getting posts:", error);
});
}, (error) => {
console.error("Error getting user:", error);
});
Promises
Creating Promises
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve({ user: "John", age: 30 });
} else {
reject(new Error("Failed to fetch data"));
}
}, 1000);
});
}
// Using the promise
fetchData()
.then(data => {
console.log("Success:", data);
})
.catch(error => {
console.log("Error:", error.message);
});
Promise Methods
// Promise.resolve()
const resolvedPromise = Promise.resolve("Success");
resolvedPromise.then(result => console.log(result)); // "Success"
// Promise.reject()
const rejectedPromise = Promise.reject(new Error("Failed"));
rejectedPromise.catch(error => console.log(error.message)); // "Failed"
// Promise.all() - All must resolve
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results)) // [1, 2, 3]
.catch(error => console.log("One failed:", error));
// Promise.race() - First to settle wins
const fast = new Promise(resolve => setTimeout(() => resolve("fast"), 100));
const slow = new Promise(resolve => setTimeout(() => resolve("slow"), 1000));
Promise.race([fast, slow])
.then(result => console.log(result)); // "fast"
// Promise.allSettled() (ES11+) - Wait for all to settle
const promises = [
Promise.resolve("success"),
Promise.reject("error"),
Promise.resolve("another success")
];
Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("Fulfilled:", result.value);
} else {
console.log("Rejected:", result.reason);
}
});
});
// Promise.any() (ES12+) - First to resolve wins
const promises2 = [
Promise.reject("error1"),
Promise.resolve("success"),
Promise.reject("error2")
];
Promise.any(promises2)
.then(result => console.log("First success:", result)) // "success"
.catch(errors => console.log("All failed:", errors));
ES2024/ES2025 Updates
// Promise.withResolvers() (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve('done'), 10);
promise.then(console.log); // 'done'
// Promise.try() (ES2025)
function mightThrow() { if (Math.random() < 0.5) throw new Error('nope'); return 42; }
Promise.try(mightThrow)
.then(value => console.log('value:', value))
.catch(err => console.error('caught:', err.message));
Async/Await (ES8+)
Basic Async Function
async function fetchUserData() {
try {
const response = await fetch('/api/user');
const userData = await response.json();
return userData;
} catch (error) {
console.error("Error fetching user data:", error);
throw error;
}
}
// Usage
async function main() {
try {
const user = await fetchUserData();
console.log("User data:", user);
} catch (error) {
console.log("Failed to get user data");
}
}
main();
Parallel Execution
async function fetchMultipleData() {
try {
// Sequential (slow)
const user = await fetch('/api/user');
const posts = await fetch('/api/posts');
// Parallel (fast)
const [userResponse, postsResponse] = await Promise.all([
fetch('/api/user'),
fetch('/api/posts')
]);
const userData = await userResponse.json();
const postsData = await postsResponse.json();
return { user: userData, posts: postsData };
} catch (error) {
console.error("Error:", error);
}
}
Error Handling with Async/Await
async function processData() {
try {
const data = await riskyOperation();
const processed = await processData(data);
await saveData(processed);
console.log("All operations completed successfully");
} catch (error) {
console.error("An error occurred:", error.message);
// Handle error appropriately
await logError(error);
} finally {
// Cleanup code
await cleanup();
}
}
Generators (ES6+)
Basic Generator
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = numberGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
Async Generators (ES9+)
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
async function consumeAsyncGenerator() {
for await (const value of asyncGenerator()) {
console.log(value);
}
}
consumeAsyncGenerator(); // 1, 2, 3
Event Loop
Understanding the Event Loop
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 1");
});
setTimeout(() => {
console.log("Timeout 2");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 2");
});
console.log("End");
// Output order:
// Start
// End
// Promise 1
// Promise 2
// Timeout 1
// Timeout 2
Real-World Example: API Calls
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async get(endpoint) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`GET ${endpoint} failed:`, error);
throw error;
}
}
async post(endpoint, data) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`POST ${endpoint} failed:`, error);
throw error;
}
}
}
// Usage
const api = new ApiClient('https://jsonplaceholder.typicode.com');
async function fetchUserAndPosts(userId) {
try {
const [user, posts] = await Promise.all([
api.get(`/users/${userId}`),
api.get(`/posts?userId=${userId}`)
]);
console.log('User:', user);
console.log('Posts:', posts);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}
fetchUserAndPosts(1);
Best Practices
1. Use Async/Await for Cleaner Code
// Instead of this:
function getUser() {
return fetch('/api/user')
.then(response => response.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`)
.then(response => response.json());
});
}
// Do this:
async function getUser() {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
}
2. Handle Errors Properly
async function robustApiCall() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'TypeError') {
// Network error
throw new Error('Network connection failed');
}
throw error; // Re-throw other errors
}
}
3. Avoid Mixing Callbacks and Promises
// Bad: mixing styles
function oldStyleFunction(callback) {
setTimeout(() => callback(null, "result"), 1000);
}
oldStyleFunction((error, result) => {
if (error) return console.error(error);
console.log(result);
});
// Good: convert to promises
function promisifiedFunction() {
return new Promise((resolve, reject) => {
oldStyleFunction((error, result) => {
if (error) reject(error);
else resolve(result);
});
});
}
promisifiedFunction().then(result => console.log(result));
Next Steps
Asynchronous programming is essential for modern JavaScript applications. Next, let's explore modules, which help organize and share code.