18. Asynchronous JavaScript Deep Dive
Why Asynchronous JavaScript Matters
Modern web apps need to perform tasks that take time — fetching data, reading files, or interacting with APIs. JavaScript handles these with asynchronous patterns, so your app stays responsive while waiting for results.
Callbacks
A callback is a function passed into another function to run later, often after an asynchronous operation completes.
function fetchData(callback) {
setTimeout(() => {
callback('Data loaded');
}, 1000);
}
fetchData((result) => {
console.log(result); // Data loaded
});While callbacks work, they can lead to nested 'callback hell' for complex sequences.
Promises
Promises represent a value that may be available now, later, or never. They provide `.then()` for success and `.catch()` for errors.
const promise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if(success) resolve('Data received');
else reject('Error occurred');
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error));Chaining Promises
Promises can be chained to perform multiple asynchronous operations in order, avoiding deeply nested callbacks.
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {
console.log(users.length);
return users[0];
})
.then(user => console.log(user.name))
.catch(error => console.error('Error fetching data:', error));Async/Await
Async functions let you write asynchronous code that looks synchronous. Use `await` to pause until a Promise resolves.
async function getUser() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
console.log(user.name);
} catch (error) {
console.error('Error:', error);
}
}
getUser();Error Handling in Async Code
Always handle errors in asynchronous code using `.catch()` with Promises or `try...catch` with async/await. Network issues, invalid URLs, or bad responses can throw errors.
async function fetchWithErrorHandling() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/invalid-url');
if(!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Caught error:', error.message);
} finally {
console.log('Fetch attempt finished');
}
}
fetchWithErrorHandling();Parallel vs Sequential Execution
Use `Promise.all` to run multiple async operations in parallel, or `await` sequentially if order matters.
async function fetchMultiple() {
const urls = [
'https://jsonplaceholder.typicode.com/users/1',
'https://jsonplaceholder.typicode.com/users/2'
];
// Parallel
const results = await Promise.all(urls.map(url => fetch(url).then(res => res.json())));
console.log(results);
// Sequential
for(const url of urls) {
const res = await fetch(url);
const data = await res.json();
console.log(data.name);
}
}
fetchMultiple();Mini Challenge
1. Fetch a list of users from the Random User API and log their first and last names. 2. Use async/await with try/catch to handle errors. 3. Fetch 3 separate endpoints in parallel and display results. 4. Demonstrate what happens if one of the Promises fails using Promise.all.