Day-26-异步编程async-await

Day 26: 异步编程async/await

📅 日期: 2026年03月18日
🎯 学习目标: 深入理解 async/await,掌握异步代码的同步写法
⏱️ 预计用时: 2-3 小时
📂 分类: 10-现代JavaScript


🎯 学习目标

通过今天的学习,你将:

  1. 理解 async/await 语法:让异步代码看起来像同步代码
  2. 掌握错误处理:try/catch、异常传播
  3. 学会并行处理:Promise.all + async/await
  4. 实践最佳实践:避免常见陷阱、性能优化
  5. 应用实战场景:API 请求、文件操作、数据库查询

💡 核心概念

1. async 函数定义

// async 函数总是返回 Promise
async function fetchUserData(userId) {
    const response = await fetch('/api/users/' + userId);
    const user = await response.json();
    return user;
}

// 等价于
function fetchUserData(userId) {
    return fetch('/api/users/' + userId)
        .then(response => response.json());
}

2. await 表达式

// await 只能在 async 函数中使用
async function example() {
    // 暂停执行,等待 Promise 解决
    const data = await fetchData();
    console.log(data);
    
    // await 后的代码会在 data 准备好后执行
    console.log('继续执行');
}

3. 错误处理

// 使用 try/catch
async function handleError() {
    try {
        const data = await riskyOperation();
        return { success: true, data };
    } catch (error) {
        console.error('错误:', error);
        return { success: false, error: error.message };
    }
}

🛠️ API 函数详解

1. async 函数

// async 函数声明
async function getData() {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

// async 箭头函数
const getUser = async (id) => {
    const response = await fetch('/api/users/' + id);
    return await response.json();
};

// async 方法
const obj = {
    async fetchData() {
        return await fetch('/api/data');
    }
};

2. await 表达式

// await 等待 Promise 解决
const result = await promise;

// 可以 await 任何 thenable 对象
const thenable = {
    then: (resolve) => resolve('value')
};
const value = await thenable;

// await 的优先级低于运算符
const sum = await promise1 + await promise2;  // 并行执行

3. 错误处理

// try/catch/finally
async function withFinally() {
    try {
        await resource.acquire();
        await doWork();
    } catch (error) {
        console.error(error);
    } finally {
        await resource.release();  // 总是执行
    }
}

// 捕获特定类型的错误
async function specificCatch() {
    try {
        await riskyOperation();
    } catch (error) {
        if (error instanceof NetworkError) {
            console.log('网络错误');
        } else if (error instanceof ValidationError) {
            console.log('验证错误');
        } else {
            throw error;  // 重新抛出
        }
    }
}

🎮 实战示例

示例 1:顺序执行异步操作

// 使用 async/await 顺序执行
async function getDashboardData() {
    const user = await getUser(1);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    
    return { user, posts, comments };
}

// 相比 then 链
function getDashboardDataOld() {
    return getUser(1)
        .then(user => getPosts(user.id))
        .then(posts => getComments(posts[0].id));
}

示例 2:并行执行异步操作

// 并行执行多个独立的异步操作
async function fetchAllData() {
    const [users, posts, comments] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/comments').then(r => r.json())
    ]);
    
    return { users, posts, comments };
}

// 使用 for...of 循环
async function fetchAllItems(urls) {
    const results = [];
    for (const url of urls) {
        const response = await fetch(url);
        const data = await response.json();
        results.push(data);
    }
    return results;
}

示例 3:超时控制

// Promise.race + async/await
async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    
    const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => {
            controller.abort();
            reject(new Error('请求超时'));
        }, timeout);
    });
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        return await response.json();
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('请求超时');
        }
        throw error;
    }
}

⚠️ 重要注意事项

1. async 函数总是返回 Promise

// 正确:可以利用返回值
const promise = async function() {
    return 'value';
};  // 返回 Promise

// 错误:忘记 await
async function wrong() {
    const result = getData();  // 必须加 await!
    console.log(result);  // 输出 Promise 对象
}

2. await 只能在 async 函数中使用

// 正确:在 async 函数中使用 await
async function correct() {
    const data = await fetchData();
    return data;
}

// 错误:在非 async 函数中使用 await
function wrong() {
    const data = await fetchData();  // SyntaxError!
    return data;
}

3. 避免 Promise 构造器反模式

// ❌ 错误:Promise 构造器反模式
async function wrong() {
    return new Promise((resolve, reject) => {
        const data = await fetchData();
        resolve(data);
    });
}

// ✅ 正确:直接 await
async function correct() {
    const data = await fetchData();
    return data;
}

4. 并行执行技巧

// 使用 Promise.all 并行
async function parallel() {
    const [result1, result2] = await Promise.all([
        operation1(),
        operation2()
    ]);
}

// 使用 for...of 并行
async function parallelLoop(items) {
    const promises = items.map(item => processItem(item));
    const results = await Promise.all(promises);
    return results;
}

🎓 实战应用场景

场景 1: API 请求

// 获取用户完整信息
async function getUserInfo(userId) {
    try {
        const [profile, posts, friends] = await Promise.all([
            fetch('/api/users/' + userId + '/profile').then(r => r.json()),
            fetch('/api/users/' + userId + '/posts').then(r => r.json()),
            fetch('/api/users/' + userId + '/friends').then(r => r.json())
        ]);
        
        return {
            profile,
            posts,
            friends
        };
    } catch (error) {
        console.error('获取用户信息失败:', error);
        throw error;
    }
}

场景 2: 文件操作

// 读取多个文件
async function loadConfigFiles() {
    const [config1, config2, config3] = await Promise.all([
    readFile('./config1.json').then(JSON.parse),
    readFile('./config2.json').then(JSON.parse),
    readFile('./config3.json').then(JSON.parse)
    ]);
    
    return { config1, config2, config3 };
}

// 顺序处理文件
async function processFiles(files) {
    for (const file of files) {
        const data = await readFile(file);
        await processFile(data);
        await writeFile('processed/' + file, data);
    }
}

场景 3: 数据库查询

// 连接池查询
async function getUserWithPosts(userId) {
    const client = await pool.connect();
    
    try {
        const user = await client.query(
            'SELECT * FROM users WHERE id = $1',
            [userId]
        );
        
        if (!user) {
            throw new Error('用户不存在');
        }
        
        const posts = await client.query(
            'SELECT * FROM posts WHERE user_id = $1 ORDER BY created_at DESC',
            [userId]
        );
        
        return { ...user, posts };
    } finally {
        client.release();
    }
}

✍️ 练习任务

基础练习(必做)

  1. async/await 转换

    • [ ] 将 then 部分转换为 async/await
    • [ ] 将回调函数转换为 async 函数
    • [ ] 处理错误情况
  2. 错误处理

    • [ ] 使用 try/catch 捕获错误
    • [ ] 区分不同类型的错误
    • [ ] 实现重试逻辑
  3. 并行执行

    • [ ] 使用 Promise.all 并行请求
    • [ ] 使用 for…of 并行处理数组
    • [ ] 处理部分失败的情况

进阶练习(推荐)

  1. 高级模式

    • Promise.allSettled + async/await
    • 实现批量操作(并行、串行混合)
    • 实现并发控制(限制并发数)
  2. 性能优化

    • 避免不必要的 await
    • 使用缓存减少请求
    • 实现请求去重
  3. 工具函数

    // 延迟函数
    const delay = ms => new Promise(resolve => 
        setTimeout(resolve, ms)
    );
    
    // 重试函数
    async function retry(fn, maxRetries = 3, delay = 1000) {
        for (let i = 0; i < maxRetries; i++) {
            try {
                return await fn();
            } catch (error) {
                if (i === maxRetries - 1) throw error;
                await delay(delay * (i + 1));
            }
        }
    }
    
    // 超时函数
    async function withTimeout(promise, timeoutMs) {
        const timeout = new Promise((_, reject) =>
            setTimeout(() => reject(new Error('超时')), timeoutMs)
        );
        return Promise.race([promise, timeout]);
    }
    

实战挑战(选做)

  1. 实现并发控制

    • 限制同时最多 N 个并发请求
    • 队列管理待处理任务
  2. 实现进度追踪

    • 报告处理进度
    • 实现进度条显示
    • 支持取消操作
  3. 实现缓存机制

    • 内存缓存
    • LocalStorage 持久化
    • 缓存失效策略

💡 常见问题 FAQ

Q1: async/await 和 Promise 有什么区别?

A:

  • async/await 是 Promise 的语法糖
  • async/await 让异步代码看起来像同步代码
  • 底层仍然使用 Promise
  • 可以混用 Promise 和 async/await

Q2: 如何处理循环中的 await?

A:

// 串行执行
for (const item of items) {
    await processItem(item);  // 等待每个完成
}

// 并行执行(限制并发)
const concurrency = 5;
for (let i = 0; i < items.length; i += concurrency) {
    const batch = items.slice(i, i + concurrency);
    await Promise.all(batch.map(processItem));
}

Q3: 如何避免阻塞事件循环?

A:

  • 不要在循环中使用 await(除非是串行场景)
  • 使用 Promise.all() 并行处理
  • 分批处理大量数据
  • 使用 setImmediate/setTimeout 让出控制权

Q4: 如何调试 async 函数?

A:

// 添加日志
async function debugExample() {
    console.log('1. 开始');
    const result1 = await step1();
    console.log('2. step1 完成');
    const result2 = await step2();
    console.log('3. step2 完成');
    console.log('结果:', { result1, result2 });
}

📚 拓拓阅读

官方文档

推荐资源

  1. You Don’t Know JS – async & performance
  2. Exploring JS – Async Functions
  3. JavaScript Async Patterns

相关工具


🎉 总结

今天我们深入学习了 async/await 异步编程,涵盖了:

async/await 语法:让异步代码像同步代码
错误处理:try/catch、异常传播
并行处理:.Promise.all + async/await
最佳实践:避免常见陷阱、性能优化
实战应用:API 请求、文件操作、数据库查询

async/await 是现代 JavaScript 异步编程的主流方式,它让异步代码更易读、更易维护。


下一节课预告: Day 27 将学习 模块化开发与构建工具,学习如何组织大型项目!

💪 加油!坚持就是胜利!