JavaScript Async Programming: Callbacks, Promises, and Async/Await

Asynchronous programming is fundamental to JavaScript. Understanding how to handle async operations separates junior developers from senior ones. This guide explains the evolution from callbacks to Promises to async/await, with practical examples for each approach.

## Why Asynchronous Programming Matters

JavaScript is single-threaded. Long-running operations would freeze the entire application. Async programming allows the main thread to continue while waiting for operations like:

- Network requests
- File operations
- Timers
- Database queries

## The Callback Era

Callbacks are functions passed as arguments to be executed later.

### Basic Example

```javascript
setTimeout(() => {
 console.log('Executed after 1 second');
}, 1000);
```

### The Callback Hell Problem

Nested callbacks create unreadable code:

```javascript
getUser(userId, (user) => {
 getPosts(user.id, (posts) => {
   getComments(posts[0].id, (comments) => {
     console.log(comments);
   });
 });
});
```

This pyramid of doom is hard to read, debug, and maintain.

## Promises: A Better Way

Promises represent eventual completion or failure of async operations.

### Creating Promises

```javascript
const fetchData = () => {
 return new Promise((resolve, reject) => {
   setTimeout(() => {
     const success = true;
     if (success) {
       resolve({ data: 'Hello World' });
     } else {
       reject(new Error('Failed to fetch'));
     }
   }, 1000);
 });
};
```

### Consuming Promises

```javascript
fetchData()
 .then(result => console.log(result))
 .catch(error => console.error(error))
 .finally(() => console.log('Done'));
```

### Chaining Promises

```javascript
getUser(userId)
 .then(user => getPosts(user.id))
 .then(posts => getComments(posts[0].id))
 .then(comments => console.log(comments))
 .catch(error => console.error(error));
```

### Promise.all

Run multiple operations in parallel:

```javascript
Promise.all([
 fetch('/api/users'),
 fetch('/api/posts')
])
 .then(([users, posts]) => {
   console.log(users, posts);
 });
```

### Promise.race

Get the first resolved Promise:

```javascript
Promise.race([
 fetchFromServer1(),
 fetchFromServer2()
]).then(result => console.log(result));
```

## Async/Await: Syntactic Sugar

Async/await makes async code look synchronous.

### Basic Syntax

```javascript
async function fetchUser() {
 try {
   const response = await fetch('/api/user');
   const user = await response.json();
   return user;
 } catch (error) {
   console.error('Error:', error);
 }
}
```

### Sequential vs Parallel

Sequential (slow):

```javascript
const user = await getUser();
const posts = await getPosts();
// Total time = time1 + time2
```

Parallel (fast):

```javascript
const [user, posts] = await Promise.all([
 getUser(),
 getPosts()
]);
// Total time = max(time1, time2)
```

### Error Handling

```javascript
async function handleRequest() {
 try {
   const result = await riskyOperation();
   return result;
 } catch (error) {
   if (error instanceof NetworkError) {
     return fallbackData();
   }
   throw error;
 }
}
```

## Common Patterns

### Retry Logic

```javascript
async function retry(fn, attempts = 3) {
 for (let i = 0; i < attempts; i++) {
   try {
     return await fn();
   } catch (error) {
     if (i === attempts - 1) throw error;
     await new Promise(r => setTimeout(r, 1000 * (i + 1)));
   }
 }
}
```

### Debouncing

```javascript
function debounce(fn, delay) {
 let timeoutId;
 return (...args) => {
   clearTimeout(timeoutId);
   timeoutId = setTimeout(() => fn(...args), delay);
 };
}
```

### Throttling

```javascript
function throttle(fn, limit) {
 let inThrottle;
 return (...args) => {
   if (!inThrottle) {
     fn(...args);
     inThrottle = true;
     setTimeout(() => inThrottle = false, limit);
   }
 };
}
```

## Best Practices

1. **Always handle errors** with try/catch or .catch()
2. **Prefer async/await** for readability
3. **Use Promise.all** for parallel operations
4. **Avoid mixing** callbacks and Promises
5. **Return Promises** from async functions

## Conclusion

Modern JavaScript async programming evolved from callbacks through Promises to async/await. Master all three to understand legacy code and write clean modern code. The key insight: async/await is syntactic sugar over Promises, which themselves manage callbacks.

评论
暂无评论