Asynchronous JavaScript
## Learning Objectives
- Understand the event loop and asynchronous programming
- Master callbacks, promises, and async/await
- Handle errors in asynchronous code
## Synchronous vs Asynchronous
### Synchronous Code
```javascript
console.log("First");
console.log("Second");
console.log("Third");
// Output: First, Second, Third (in order)
```
### Asynchronous Code
```javascript
console.log("First");
setTimeout(() => console.log("Second"), 1000);
console.log("Third");
// Output: First, Third, Second (after 1 second)
```
## The Event Loop
JavaScript is single-threaded but uses an event loop to handle asynchronous operations.
```javascript
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
// Output: Start, End, Promise, Timeout
```
Order of execution:
1. Synchronous code first
2. Microtasks (Promises) next
3. Macrotasks (setTimeout, setInterval) last
## Callbacks
```javascript
function fetchData(callback) {
setTimeout(() => {
const data = { name: "Alice", age: 30 };
callback(null, data);
}, 1000);
}
function handleError(error) {
console.error("Error:", error);
}
fetchData((error, data) => {
if (error) {
handleError(error);
return;
}
console.log("Data:", data);
});
```
### Callback Hell
```javascript
fetchData((err, data) => {
if (err) return handleError(err);
processData(data, (err, result) => {
if (err) return handleError(err);
saveResult(result, (err) => {
if (err) return handleError(err);
sendNotification((err) => {
if (err) return handleError(err);
console.log("Done!");
});
});
});
});
```
## Promises
```javascript
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve({ data: "Success!" });
} else {
reject(new Error("Failed!"));
}
});
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Completed"));
```
### Promise States
- **Pending**: Initial state
- **Fulfilled**: Operation completed successfully
- **Rejected**: Operation failed
### Creating Promises
```javascript
// From callback
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Data"), 1000);
});
}
// From value
const resolvedPromise = Promise.resolve(42);
const rejectedPromise = Promise.reject(new Error("Oops!"));
// From existing promise
const promiseToPromise = Promise.resolve(fetchData());
```
### Promise Chain
```javascript
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => filterPublicPosts(posts))
.then(publicPosts => sortByDate(publicPosts))
.then(sorted => displayPosts(sorted))
.catch(error => displayError(error));
```
### Promise.all()
```javascript
const promises = [
fetchUser(1),
fetchPosts(1),
fetchComments(1)
];
Promise.all(promises)
.then(([user, posts, comments]) => {
console.log(user, posts, comments);
})
.catch(error => console.error("One failed:", error));
```
### Promise.allSettled()
```javascript
const promises = [
fetchUser(1),
fetchPosts(1),
fetchSomeFailingOperation()
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`${index}: ${result.value}`);
} else {
console.log(`${index}: ${result.reason}`);
}
});
});
```
### Promise.race()
```javascript
const slow = new Promise(resolve => setTimeout(() => resolve("slow"), 1000));
const fast = new Promise(resolve => setTimeout(() => resolve("fast"), 500));
Promise.race([slow, fast])
.then(result => console.log(result)); // "fast" (whichever resolves first)
```
### Promise.any()
```javascript
const promises = [
fetchWithTimeout("/api/1", { timeout: 1000 }),
fetchWithTimeout("/api/2", { timeout: 2000 }),
fetchWithTimeout("/api/3", { timeout: 3000 })
];
Promise.any(promises)
.then(result => console.log(result))
.catch(errors => console.log("All failed:", errors));
```
## async/await
### Basic Syntax
```javascript
async function fetchData() {
const response = await fetch("/api/data");
const data = await response.json();
return data;
}
fetchData().then(console.log);
```
### async Function Returns
```javascript
async function getValue() {
return 42; // Returns Promise.resolve(42)
}
getValue().then(console.log); // 42
async function getError() {
throw new Error("Oops!");
} // Returns Promise.reject(Error("Oops!"))
```
### await Expression
```javascript
// await pauses execution until promise settles
const result = await somePromise;
// Equivalent to
somePromise.then(result => {
// use result here
});
```
### Error Handling with try/catch
```javascript
async function fetchData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch failed:", error);
throw error;
}
}
// Or catch at call site
try {
const data = await fetchData();
} catch (error) {
console.error("Error:", error);
}
```
### Parallel Execution
```javascript
async function fetchAll() {
// Sequential (slower)
const user = await fetchUser();
const posts = await fetchPosts();
// Parallel (faster)
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
}
```
### Sequential vs Parallel
```javascript
// Sequential - takes 3 seconds (1s + 1s + 1s)
async function sequential() {
const a = await task1(); // 1s
const b = await task2(); // 1s
const c = await task3(); // 1s
return [a, b, c];
}
// Parallel - takes 1 second (all run together)
async function parallel() {
const [a, b, c] = await Promise.all([
task1(), // 1s
task2(), // 1s
task3() // 1s
]);
return [a, b, c];
}
```
### Common Patterns
```javascript
// Process array with async function
const urls = ["/api/1", "/api/2", "/api/3"];
async function fetchAll() {
return Promise.all(urls.map(url => fetch(url)));
}
// Sequential processing when order matters
async function fetchSequential() {
const results = [];
for (const url of urls) {
const result = await fetch(url);
results.push(result);
}
return results;
}
```
## Converting Callbacks to Promises
```javascript
// Callback style
function fetchData(callback) {
setTimeout(() => callback(null, "data"), 1000);
}
// To Promise
function fetchData() {
return new Promise((resolve, reject) => {
fetchData((error, result) => {
if (error) reject(error);
else resolve(result);
});
});
}
// Using util.promisify (Node.js)
const { promisify } = require("util");
const fs = require("fs");
const readFile = promisify(fs.readFile);
```
## Summary
- JavaScript is single-threaded but handles async via event loop
- Callbacks are the original async pattern but lead to callback hell
- Promises provide better error handling and chaining
- `async/await` makes asynchronous code look synchronous
- Use `Promise.all()` for parallel execution when possible
- Always handle errors in async code with `.catch()` or try/catch
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →