异步编程基础:掌握JavaScript的异步世界

今天我们要进入JavaScript最重要也是最具挑战性的领域——异步编程!从回调函数到Promise,再到async/await,让我们一步步征服异步编程的世界。

🎯 本周学习目标

✅ 理解同步与异步的区别和应用场景
✅ 掌握回调函数的使用和回调地狱问题
✅ 深入学习Promise的创建和使用
✅ 掌握async/await语法糖
✅ 完成API数据获取实战项目


⏰ 第一部分:同步与异步概念

1️⃣ 同步代码执行

console.log('开始执行');

function syncFunction() {
    console.log('同步函数执行中...');
    // 模拟耗时操作
    let start = Date.now();
    while (Date.now() - start < 2000) {
        // 阻塞2秒
    }
    console.log('同步函数执行完毕');
    return '同步结果';
}

console.log('调用同步函数');
let result = syncFunction();
console.log('结果:', result);
console.log('继续执行其他代码');

// 输出顺序:
// 开始执行
// 调用同步函数
// 同步函数执行中...
// (等待2秒)
// 同步函数执行完毕
// 结果: 同步结果
// 继续执行其他代码

2️⃣ 异步代码执行

console.log('开始执行');

// setTimeout - 最基本的异步函数
setTimeout(() => {
    console.log('异步操作完成');
}, 2000);

console.log('继续执行其他代码');

// 输出顺序:
// 开始执行
// 继续执行其他代码
// (2秒后)
// 异步操作完成

// 更多异步API示例
console.log('1. 同步代码');

setTimeout(() => console.log('2. setTimeout 0ms'), 0);
setTimeout(() => console.log('3. setTimeout 100ms'), 100);

setImmediate(() => console.log('4. setImmediate')); // Node.js环境

Promise.resolve().then(() => console.log('5. Promise.resolve'));

console.log('6. 同步代码结束');

// 输出顺序(浏览器环境):
// 1. 同步代码
// 6. 同步代码结束
// 5. Promise.resolve
// 2. setTimeout 0ms
// 3. setTimeout 100ms

3️⃣ 事件循环机制

// 理解事件循环
console.log('=== 事件循环示例 ===');

// 宏任务 (Macro Task)
setTimeout(() => console.log('宏任务1: setTimeout'), 0);

// 微任务 (Micro Task)
Promise.resolve().then(() => console.log('微任务1: Promise'));

// 同步代码
console.log('同步代码1');

// 更多微任务
Promise.resolve().then(() => {
    console.log('微任务2: Promise');
    return Promise.resolve();
}).then(() => {
    console.log('微任务3: Promise链');
});

// 更多宏任务
setTimeout(() => console.log('宏任务2: setTimeout'), 0);

console.log('同步代码2');

// 执行顺序:
// 同步代码1
// 同步代码2
// 微任务1: Promise
// 微任务2: Promise
// 微任务3: Promise链
// 宏任务1: setTimeout
// 宏任务2: setTimeout

📞 第二部分:回调函数

1️⃣ 回调函数基础

// 简单回调函数
function greet(name, callback) {
    console.log(`Hello, ${name}!`);
    callback();
}

function afterGreeting() {
    console.log('Nice to meet you!');
}

greet('张三', afterGreeting);

// 匿名回调函数
greet('李四', function() {
    console.log('How are you?');
});

// 箭头函数回调
greet('王五', () => {
    console.log('Have a great day!');
});

// 带参数的回调
function processData(data, successCallback, errorCallback) {
    if (data && data.length > 0) {
        successCallback(data.toUpperCase());
    } else {
        errorCallback('数据为空或无效');
    }
}

processData('hello', 
    (result) => console.log('成功:', result),
    (error) => console.log('错误:', error)
);

2️⃣ 异步回调函数

// 模拟异步操作
function fetchUserData(userId, callback) {
    console.log(`开始获取用户 ${userId} 的数据...`);
    
    // 模拟网络请求延迟
    setTimeout(() => {
        // 模拟随机成功/失败
        if (Math.random() > 0.3) {
            const userData = {
                id: userId,
                name: `用户${userId}`,
                email: `user${userId}@example.com`
            };
            callback(null, userData); // 第一个参数是错误,第二个是数据
        } else {
            callback(new Error('网络请求失败'), null);
        }
    }, 1000);
}

// 使用异步回调
fetchUserData(123, (error, data) => {
    if (error) {
        console.log('获取用户数据失败:', error.message);
    } else {
        console.log('获取用户数据成功:', data);
    }
});

// 文件读取示例(Node.js)
const fs = require('fs'); // 仅在Node.js环境中可用

function readFileAsync(filename, callback) {
    fs.readFile(filename, 'utf8', (err, data) => {
        if (err) {
            callback(err, null);
        } else {
            callback(null, data);
        }
    });
}

3️⃣ 回调地狱问题

// 回调地狱示例
function getUserData(userId, callback) {
    setTimeout(() => {
        callback(null, { id: userId, name: `用户${userId}` });
    }, 1000);
}

function getUserPosts(userId, callback) {
    setTimeout(() => {
        callback(null, [`帖子1`, `帖子2`]);
    }, 1000);
}

function getPostComments(postId, callback) {
    setTimeout(() => {
        callback(null, [`评论1`, `评论2`]);
    }, 1000);
}

// 嵌套回调 - 回调地狱
getUserData(1, (err, user) => {
    if (err) {
        console.log('错误:', err);
        return;
    }
    
    console.log('用户数据:', user);
    
    getUserPosts(user.id, (err, posts) => {
        if (err) {
            console.log('错误:', err);
            return;
        }
        
        console.log('用户帖子:', posts);
        
        getPostComments(posts[0], (err, comments) => {
            if (err) {
                console.log('错误:', err);
                return;
            }
            
            console.log('帖子评论:', comments);
            
            // 如果还有更多嵌套...这就是回调地狱!
        });
    });
});

// 改进:使用命名函数减少嵌套
function handleUserData(err, user) {
    if (err) return console.log('错误:', err);
    console.log('用户数据:', user);
    getUserPosts(user.id, handleUserPosts);
}

function handleUserPosts(err, posts) {
    if (err) return console.log('错误:', err);
    console.log('用户帖子:', posts);
    getPostComments(posts[0], handlePostComments);
}

function handlePostComments(err, comments) {
    if (err) return console.log('错误:', err);
    console.log('帖子评论:', comments);
}

getUserData(1, handleUserData);

🤝 第三部分:Promise详解

1️⃣ Promise基础

// 创建Promise
const myPromise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        const success = Math.random() > 0.5;
        
        if (success) {
            resolve('操作成功!');
        } else {
            reject(new Error('操作失败!'));
        }
    }, 1000);
});

// 使用Promise
myPromise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.log('失败:', error.message);
    })
    .finally(() => {
        console.log('操作完成(无论成功失败)');
    });

// Promise状态
console.log('Promise状态:', myPromise); // pending

// Promise的三种状态:
// 1. pending(待定)- 初始状态
// 2. fulfilled(已兑现)- 操作成功
// 3. rejected(已拒绝)- 操作失败

2️⃣ Promise链式调用

// 将回调地狱改写为Promise
function getUserDataPromise(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId > 0) {
                resolve({ id: userId, name: `用户${userId}` });
            } else {
                reject(new Error('无效的用户ID'));
            }
        }, 1000);
    });
}

function getUserPostsPromise(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([`用户${userId}的帖子1`, `用户${userId}的帖子2`]);
        }, 1000);
    });
}

function getPostCommentsPromise(postTitle) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([`${postTitle}的评论1`, `${postTitle}的评论2`]);
        }, 1000);
    });
}

// Promise链式调用
getUserDataPromise(1)
    .then(user => {
        console.log('用户数据:', user);
        return getUserPostsPromise(user.id);
    })
    .then(posts => {
        console.log('用户帖子:', posts);
        return getPostCommentsPromise(posts[0]);
    })
    .then(comments => {
        console.log('帖子评论:', comments);
    })
    .catch(error => {
        console.log('发生错误:', error.message);
    });

// 返回值的处理
Promise.resolve(42)
    .then(value => {
        console.log('值:', value); // 42
        return value * 2;
    })
    .then(value => {
        console.log('翻倍后:', value); // 84
        return Promise.resolve(value + 10);
    })
    .then(value => {
        console.log('最终值:', value); // 94
    });

3️⃣ Promise静态方法

// Promise.resolve() - 创建已解决的Promise
const resolvedPromise = Promise.resolve('立即解决');
resolvedPromise.then(value => console.log(value));

// Promise.reject() - 创建已拒绝的Promise
const rejectedPromise = Promise.reject(new Error('立即拒绝'));
rejectedPromise.catch(error => console.log(error.message));

// Promise.all() - 等待所有Promise完成
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
    .then(values => {
        console.log('所有Promise完成:', values); // [1, 2, 3]
    })
    .catch(error => {
        console.log('有Promise失败:', error);
    });

// Promise.all() 失败示例
const fastPromise = Promise.resolve('快速完成');
const slowPromise = new Promise(resolve => setTimeout(() => resolve('慢速完成'), 2000));
const failPromise = Promise.reject(new Error('失败了'));

Promise.all([fastPromise, slowPromise, failPromise])
    .then(values => {
        console.log('不会执行到这里');
    })
    .catch(error => {
        console.log('Promise.all失败:', error.message); // 立即失败
    });

// Promise.allSettled() - 等待所有Promise完成(无论成功失败)
Promise.allSettled([fastPromise, slowPromise, failPromise])
    .then(results => {
        console.log('所有Promise都已完成:');
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index} 成功:`, result.value);
            } else {
                console.log(`Promise ${index} 失败:`, result.reason.message);
            }
        });
    });

// Promise.race() - 返回第一个完成的Promise
const slowResolve = new Promise(resolve => setTimeout(() => resolve('慢速成功'), 2000));
const fastResolve = new Promise(resolve => setTimeout(() => resolve('快速成功'), 1000));

Promise.race([slowResolve, fastResolve])
    .then(value => {
        console.log('最快完成的Promise:', value); // '快速成功'
    });

// Promise.any() - 返回第一个成功的Promise(ES2021)
const failFast = Promise.reject(new Error('快速失败'));
const failSlow = Promise.reject(new Error('慢速失败'));
const succeedSlow = new Promise(resolve => setTimeout(() => resolve('慢速成功'), 2000));

Promise.any([failFast, failSlow, succeedSlow])
    .then(value => {
        console.log('第一个成功的Promise:', value); // '慢速成功'
    })
    .catch(error => {
        console.log('所有Promise都失败了:', error);
    });

🎭 第四部分:async/await语法

1️⃣ async/await基础

// async函数声明
async function fetchData() {
    return '数据获取成功';
}

// async函数总是返回Promise
console.log(fetchData()); // Promise对象

fetchData().then(data => console.log(data)); // '数据获取成功'

// await关键字
async function getData() {
    try {
        const result = await fetchData();
        console.log('结果:', result);
        return result;
    } catch (error) {
        console.log('错误:', error);
    }
}

getData();

// 将Promise改写为async/await
async function getUserFlow() {
    try {
        console.log('开始获取用户数据...');
        
        const user = await getUserDataPromise(1);
        console.log('用户数据:', user);
        
        const posts = await getUserPostsPromise(user.id);
        console.log('用户帖子:', posts);
        
        const comments = await getPostCommentsPromise(posts[0]);
        console.log('帖子评论:', comments);
        
        return { user, posts, comments };
    } catch (error) {
        console.log('发生错误:', error.message);
        throw error; // 重新抛出错误
    }
}

getUserFlow();

2️⃣ 错误处理

// try-catch错误处理
async function handleErrors() {
    try {
        const result1 = await Promise.resolve('成功1');
        console.log(result1);
        
        const result2 = await Promise.reject(new Error('失败了'));
        console.log('不会执行到这里');
        
    } catch (error) {
        console.log('捕获到错误:', error.message);
    } finally {
        console.log('清理工作');
    }
}

handleErrors();

// 多个await的错误处理
async function multipleAwaits() {
    try {
        const user = await getUserDataPromise(1);
        const posts = await getUserPostsPromise(user.id);
        const comments = await getPostCommentsPromise(posts[0]);
        
        return { user, posts, comments };
    } catch (error) {
        // 任何一个await失败都会被这里捕获
        console.log('操作失败:', error.message);
        return null;
    }
}

// 单独处理每个await的错误
async function individualErrorHandling() {
    let user, posts, comments;
    
    try {
        user = await getUserDataPromise(1);
    } catch (error) {
        console.log('获取用户数据失败:', error.message);
        return;
    }
    
    try {
        posts = await getUserPostsPromise(user.id);
    } catch (error) {
        console.log('获取帖子失败:', error.message);
        posts = []; // 使用默认值
    }
    
    try {
        comments = await getPostCommentsPromise(posts[0] || '默认帖子');
    } catch (error) {
        console.log('获取评论失败:', error.message);
        comments = [];
    }
    
    return { user, posts, comments };
}

3️⃣ 并发处理

// 串行执行(一个接一个)
async function serialExecution() {
    console.time('串行执行');
    
    const result1 = await new Promise(resolve => setTimeout(() => resolve('结果1'), 1000));
    const result2 = await new Promise(resolve => setTimeout(() => resolve('结果2'), 1000));
    const result3 = await new Promise(resolve => setTimeout(() => resolve('结果3'), 1000));
    
    console.timeEnd('串行执行'); // 约3秒
    return [result1, result2, result3];
}

// 并行执行(同时开始)
async function parallelExecution() {
    console.time('并行执行');
    
    const promise1 = new Promise(resolve => setTimeout(() => resolve('结果1'), 1000));
    const promise2 = new Promise(resolve => setTimeout(() => resolve('结果2'), 1000));
    const promise3 = new Promise(resolve => setTimeout(() => resolve('结果3'), 1000));
    
    const results = await Promise.all([promise1, promise2, promise3]);
    
    console.timeEnd('并行执行'); // 约1秒
    return results;
}

// 使用Promise.all与await结合
async function efficientDataFetching() {
    try {
        // 同时发起多个请求
        const [userData, userPosts, userFriends] = await Promise.all([
            getUserDataPromise(1),
            getUserPostsPromise(1),
            Promise.resolve(['朋友1', '朋友2']) // 模拟获取朋友列表
        ]);
        
        console.log('用户数据:', userData);
        console.log('用户帖子:', userPosts);
        console.log('用户朋友:', userFriends);
        
        return { userData, userPosts, userFriends };
    } catch (error) {
        console.log('获取数据失败:', error.message);
    }
}

// 限制并发数量
async function limitedConcurrency(urls, limit = 3) {
    const results = [];
    
    for (let i = 0; i < urls.length; i += limit) {
        const batch = urls.slice(i, i + limit);
        const batchPromises = batch.map(url => fetch(url));
        const batchResults = await Promise.all(batchPromises);
        results.push(...batchResults);
    }
    
    return results;
}

🌐 第五部分:实战项目 - API数据获取

让我们创建一个完整的API数据获取和展示系统:

// API数据获取类
class APIClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.defaultHeaders = {
            'Content-Type': 'application/json'
        };
    }
    
    // 基础请求方法
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const config = {
            headers: { ...this.defaultHeaders, ...options.headers },
            ...options
        };
        
        try {
            console.log(`发起请求: ${config.method || 'GET'} ${url}`);
            
            const response = await fetch(url, config);
            
            if (!response.ok) {
                throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
            }
            
            const data = await response.json();
            console.log('请求成功:', data);
            return data;
            
        } catch (error) {
            console.error('请求失败:', error.message);
            throw error;
        }
    }
    
    // GET请求
    async get(endpoint, params = {}) {
        const queryString = new URLSearchParams(params).toString();
        const url = queryString ? `${endpoint}?${queryString}` : endpoint;
        return this.request(url);
    }
    
    // POST请求
    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    // PUT请求
    async put(endpoint, data) {
        return this.request(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }
    
    // DELETE请求
    async delete(endpoint) {
        return this.request(endpoint, {
            method: 'DELETE'
        });
    }
}

// 用户数据管理类
class UserManager {
    constructor() {
        this.api = new APIClient('https://jsonplaceholder.typicode.com');
        this.cache = new Map();
        this.loadingStates = new Map();
    }
    
    // 获取用户列表
    async getUsers() {
        const cacheKey = 'users';
        
        // 检查缓存
        if (this.cache.has(cacheKey)) {
            console.log('从缓存获取用户列表');
            return this.cache.get(cacheKey);
        }
        
        // 检查是否正在加载
        if (this.loadingStates.get(cacheKey)) {
            console.log('用户列表正在加载中...');
            return this.loadingStates.get(cacheKey);
        }
        
        // 开始加载
        const loadingPromise = this.api.get('/users');
        this.loadingStates.set(cacheKey, loadingPromise);
        
        try {
            const users = await loadingPromise;
            this.cache.set(cacheKey, users);
            return users;
        } finally {
            this.loadingStates.delete(cacheKey);
        }
    }
    
    // 获取单个用户
    async getUser(userId) {
        const cacheKey = `user_${userId}`;
        
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }
        
        try {
            const user = await this.api.get(`/users/${userId}`);
            this.cache.set(cacheKey, user);
            return user;
        } catch (error) {
            if (error.message.includes('404')) {
                throw new Error(`用户 ${userId} 不存在`);
            }
            throw error;
        }
    }
    
    // 获取用户帖子
    async getUserPosts(userId) {
        const cacheKey = `posts_${userId}`;
        
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }
        
        const posts = await this.api.get('/posts', { userId });
        this.cache.set(cacheKey, posts);
        return posts;
    }
    
    // 获取用户完整信息(用户+帖子)
    async getUserProfile(userId) {
        try {
            // 并行获取用户信息和帖子
            const [user, posts] = await Promise.all([
                this.getUser(userId),
                this.getUserPosts(userId)
            ]);
            
            return {
                ...user,
                posts,
                postCount: posts.length
            };
        } catch (error) {
            console.error(`获取用户 ${userId} 的完整信息失败:`, error.message);
            throw error;
        }
    }
    
    // 搜索用户
    async searchUsers(query) {
        const users = await this.getUsers();
        const lowerQuery = query.toLowerCase();
        
        return users.filter(user => 
            user.name.toLowerCase().includes(lowerQuery) ||
            user.email.toLowerCase().includes(lowerQuery) ||
            user.username.toLowerCase().includes(lowerQuery)
        );
    }
    
    // 批量获取用户信息
    async getBatchUsers(userIds, batchSize = 3) {
        const results = [];
        
        for (let i = 0; i < userIds.length; i += batchSize) {
            const batch = userIds.slice(i, i + batchSize);
            console.log(`处理批次 ${Math.floor(i / batchSize) + 1}: 用户 ${batch.join(', ')}`);
            
            const batchPromises = batch.map(id => 
                this.getUser(id).catch(error => ({ error: error.message, id }))
            );
            
            const batchResults = await Promise.all(batchPromises);
            results.push(...batchResults);
            
            // 避免请求过于频繁
            if (i + batchSize < userIds.length) {
                await new Promise(resolve => setTimeout(resolve, 100));
            }
        }
        
        return results;
    }
    
    // 清除缓存
    clearCache() {
        this.cache.clear();
        console.log('缓存已清除');
    }
}

// 使用示例
async function demonstrateAsyncProgramming() {
    const userManager = new UserManager();
    
    try {
        console.log('=== 异步编程演示 ===\n');
        
        // 1. 获取用户列表
        console.log('1. 获取用户列表');
        const users = await userManager.getUsers();
        console.log(`获取到 ${users.length} 个用户\n`);
        
        // 2. 获取特定用户信息
        console.log('2. 获取用户详细信息');
        const userProfile = await userManager.getUserProfile(1);
        console.log(`用户 ${userProfile.name}${userProfile.postCount} 篇帖子\n`);
        
        // 3. 搜索用户
        console.log('3. 搜索用户');
        const searchResults = await userManager.searchUsers('Bret');
        console.log(`搜索结果: ${searchResults.length} 个用户\n`);
        
        // 4. 批量获取用户
        console.log('4. 批量获取用户信息');
        const batchResults = await userManager.getBatchUsers([1, 2, 3, 999]);
        const successCount = batchResults.filter(result => !result.error).length;
        console.log(`批量获取完成: ${successCount} 个成功\n`);
        
        // 5. 演示缓存效果
        console.log('5. 演示缓存效果');
        console.time('第二次获取用户列表');
        await userManager.getUsers();
        console.timeEnd('第二次获取用户列表');
        
    } catch (error) {
        console.error('演示过程中发生错误:', error.message);
    }
}

// 运行演示
demonstrateAsyncProgramming();

// 错误处理和重试机制
class RetryableAPIClient extends APIClient {
    async requestWithRetry(endpoint, options = {}, maxRetries = 3) {
        let lastError;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                return await this.request(endpoint, options);
            } catch (error) {
                lastError = error;
                console.log(`请求失败 (尝试 ${attempt}/${maxRetries}):`, error.message);
                
                if (attempt < maxRetries) {
                    const delay = Math.pow(2, attempt) * 1000; // 指数退避
                    console.log(`等待 ${delay}ms 后重试...`);
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
            }
        }
        
        throw new Error(`请求失败,已重试 ${maxRetries} 次: ${lastError.message}`);
    }
}

💡 本周总结

🎯 核心知识点

  1. 异步概念:同步vs异步、事件循环机制
  2. 回调函数:基础用法和回调地狱问题
  3. Promise:创建、链式调用、静态方法
  4. async/await:语法糖、错误处理、并发控制
  5. 实际应用:API请求、数据处理、缓存机制

📝 最佳实践

✅ 优先使用async/await而不是Promise.then
✅ 合理使用Promise.all进行并发处理
✅ 总是处理异步操作的错误
✅ 避免在循环中使用await(除非需要串行)
✅ 使用缓存减少重复请求

🤔 思考题

  1. 什么时候使用Promise.all,什么时候使用Promise.allSettled?
  2. 如何实现请求的超时和重试机制?
  3. async/await相比Promise有什么优势和劣势?

📚 下周预告

下周我们将学习 ES6+新特性,掌握现代JavaScript的强大功能!

内容包括:

  • 箭头函数和模板字符串
  • let/const和块级作用域
  • 类和继承
  • 模块化编程

🎉 写在最后

异步编程是JavaScript的精髓,掌握它让你能够构建高性能的现代Web应用!

如果这篇文章对你有帮助:
👍 请点个赞,让更多人看到
🔄 转发分享,帮助更多学习异步编程的朋友
💬 评论区留言,分享你的异步编程经验


关于作者
每周更新实用的前端教程,从入门到进阶
关注我,一起在前端的道路上成长!


#JavaScript #异步编程 #Promise #AsyncAwait #API请求

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐