一、async/await 的出现背景
1. 异步编程的演进历程
-
回调函数时代:
getUser(id, function(user) { getPosts(user, function(posts) { console.log(posts); }); });- 问题:回调地狱,错误处理困难
-
Promise 时代:
getUser(id) .then(user => getPosts(user)) .then(posts => console.log(posts)) .catch(err => console.error(err));- 改进:链式调用,统一错误处理
- 局限:仍然需要回调,复杂逻辑不够直观
-
Generator + Promise:
function* gen() { const user = yield getUser(id); const posts = yield getPosts(user); console.log(posts); }- 问题:需要外部执行器,不够直接
2. async/await 的解决方案
async/await 是建立在 Promise 基础上的语法糖,它:
- 让异步代码看起来像同步代码
- 保留 Promise 的所有优点
- 解决回调地狱问题
- 提供更直观的错误处理
二、基本语法与工作原理
1. async 函数
async 关键字用于声明一个异步函数:
async function fetchData() {
return 'data'; // 等价于 return Promise.resolve('data')
}
// 调用
fetchData().then(data => console.log(data)); // 'data'
特性:
- 总是返回 Promise
- 返回值会被自动包装为 Promise
- 可以包含
await表达式
2. await 表达式
await 关键字用于等待 Promise 的解决:
async function getUserPosts(userId) {
const user = await getUser(userId); // 等待Promise解决
const posts = await getPosts(user.id); // 使用上一步结果
return posts;
}
规则:
- 只能在 async 函数中使用
- 暂停 async 函数的执行,直到 Promise 完成
- 返回 Promise 解决的值
- 如果 Promise 被拒绝,抛出拒绝原因(可用 try-catch 捕获)
三、错误处理机制
1. try-catch 方式
async function fetchWithRetry() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('Fetch failed:', error);
// 重试逻辑
return fetchWithRetry();
}
}
2. 结合 Promise.catch()
async function fetchData() {
const response = await fetch('api/data').catch(err => {
console.error('Network error:', err);
throw err; // 继续传递错误
});
return response.json();
}
3. 错误处理模式比较
| 方式 | 优点 | 缺点 |
|---|---|---|
| try-catch | 同步代码风格,集中处理 | 可能嵌套层级深 |
| .catch() | 链式调用,灵活 | 破坏同步代码风格 |
| 混合使用 | 根据场景选择最佳方式 | 需要风格统一 |
四、执行流程与控制
1. 顺序执行
async function sequential() {
const a = await task1(); // 等待完成
const b = await task2(); // 然后执行
return a + b;
}
2. 并行执行
async function parallel() {
const [a, b] = await Promise.all([task1(), task2()]);
return a + b;
}
3. 循环中的 await
async function processArray(array) {
for (const item of array) {
await processItem(item); // 顺序处理
}
// 或者并行处理
await Promise.all(array.map(item => processItem(item)));
}
五、高级用法与模式
1. async 立即执行函数
(async () => {
const data = await fetchData();
console.log(data);
})();
2. 类中的 async 方法
class ApiClient {
async getData() {
const response = await fetch('/api/data');
return response.json();
}
}
3. await 与 thenable 对象
// 任何实现了 then 方法的对象都可以被 await
const thenable = {
then(resolve) {
setTimeout(() => resolve('done'), 1000);
}
};
async function test() {
const result = await thenable;
console.log(result); // 1秒后输出 'done'
}
4. 顶层 await (ES2022)
// 模块中直接使用
const data = await fetchData();
console.log(data);
// 注意:只能在模块中使用,不能在普通脚本中
六、常见陷阱与最佳实践
1. 常见错误
-
忘记 await:
async function example() { const promise = fetchData(); // 忘记await console.log(promise); // 输出Promise对象而非结果 } -
过度顺序化:
// 低效写法 async function slow() { const a = await task1(); // 等待 const b = await task2(); // 再等待 return a + b; } // 高效写法 async function fast() { const [a, b] = await Promise.all([task1(), task2()]); return a + b; } -
错误处理遗漏:
// 错误可能被忽略 async function risky() { const data = await fetchData(); // 如果拒绝,错误会向上抛出 // 如果没有调用方处理,会导致未处理的Promise拒绝 }
2. 最佳实践
-
明确错误处理:
// 方式1:try-catch async function safe1() { try { return await fetchData(); } catch (err) { console.error(err); return fallbackData; } } // 方式2:.catch() async function safe2() { return await fetchData().catch(err => { console.error(err); return fallbackData; }); } -
合理并行化:
async function optimal() { // 无依赖的任务并行执行 const [user, product] = await Promise.all([ fetchUser(), fetchProduct() ]); // 有依赖的任务顺序执行 const order = await createOrder(user, product); return order; } -
性能优化:
// 提前启动Promise async function optimized() { const userPromise = fetchUser(); // 立即启动,不等待 const productPromise = fetchProduct(); // 需要时await const user = await userPromise; const product = await productPromise; return { user, product }; }
七、与生成器函数的比较
1. 实现原理相似性
async/await 本质上是 Generator + Promise 的语法糖:
// async/await 版本
async function example() {
const a = await task1();
const b = await task2();
return a + b;
}
// 生成器等效实现
function* generatorExample() {
const a = yield task1();
const b = yield task2();
return a + b;
}
// 需要外部执行器来运行生成器
2. 主要区别
| 特性 | async/await | 生成器 |
|---|---|---|
| 返回值 | 总是Promise | 任意值 |
| 执行控制 | 自动执行 | 需要外部执行器 |
| 目的 | 专门用于异步 | 通用迭代控制 |
| 语法 | 更简洁 | 更灵活 |
八、浏览器兼容性与转译
1. 兼容性情况
- 现代浏览器全面支持
- Node.js 7.6+ 原生支持
- 旧环境需要转译
2. Babel 转译
async/await 通过 Babel 可以转换为 ES5 代码:
// 转换前
async function fetchData() {
const res = await fetch('/data');
return res.json();
}
// 转换后(简化版)
function fetchData() {
return _asyncToGenerator(function* () {
const res = yield fetch('/data');
return res.json();
})();
}
3. 性能考虑
- 原生 async/await 性能接近原生 Promise
- 转译后的代码会有额外开销
- 在关键性能路径谨慎使用
九、实际应用场景
1. API 请求处理
async function fetchUserWithPosts(userId) {
try {
const user = await fetch(`/users/${userId}`);
const posts = await fetch(`/users/${userId}/posts`);
return { user, posts };
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
2. 文件操作(Node.js)
const fs = require('fs').promises;
async function processFiles() {
const content1 = await fs.readFile('file1.txt', 'utf8');
const content2 = await fs.readFile('file2.txt', 'utf8');
await fs.writeFile('combined.txt', content1 + content2);
}
3. 数据库操作
async function createUserWithProfile(userData, profileData) {
const connection = await db.getConnection();
try {
await connection.beginTransaction();
const user = await connection.query('INSERT INTO users SET ?', userData);
const profile = await connection.query(
'INSERT INTO profiles SET ?',
{ ...profileData, userId: user.insertId }
);
await connection.commit();
return { user, profile };
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
4. 异步初始化
class App {
static async init() {
this.cache = await loadCache();
this.db = await connectDatabase();
this.server = await startServer();
}
}
// 使用
App.init().then(() => {
console.log('Application started');
});
十、总结
async/await 是现代 JavaScript 异步编程的核心特性:
-
核心优势:
- 同步风格的异步代码
- 更清晰的错误处理
- 更好的可读性和可维护性
-
关键要点:
- async 函数总是返回 Promise
- await 暂停执行直到 Promise 完成
- 使用 try-catch 处理错误
- 合理组合顺序和并行执行
-
适用场景:
- 任何基于 Promise 的异步操作
- 需要清晰流程控制的异步逻辑
- 复杂的异步数据流处理
-
注意事项:
- 避免过度顺序化影响性能
- 不要忘记错误处理
- 理解底层 Promise 机制