JavaScript async/await 完全指南
面向初学者的异步编程详解 – 用最简单的方式理解现代JavaScript的异步操作
为什么要使用 async/await?
在 JavaScript 中,许多操作(如:获取网络数据、读取文件、数据库操作等)需要时间来完成。这些操作不能立即返回结果,我们称之为异步操作。
在 async/await 出现之前,我们主要使用回调函数和 Promise 来处理异步操作,但它们有一些缺点:
回调地狱 (Callback Hell)
嵌套多层回调函数,代码难以阅读和维护:
getMoreData(a, function(b) {
getMoreData(b, function(c) {
// 更多回调…
});
});
});
Promise 的改进
Promise 解决了回调地狱的问题,但代码仍然不够直观:
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => {
// 处理最终结果
})
.catch(error => {
console.error(error);
});
async/await 是 JavaScript 异步编程的终极解决方案,它让异步代码看起来和同步代码一样直观,同时保留了异步操作的非阻塞特性。
理解 async 函数
async
关键字用于声明一个函数是异步的:
async function fetchData() {
// 函数体
}
重要特性
1. async 函数总是返回一个 Promise:无论函数返回什么值,它都会被自动包装在一个 Promise 中
2. 如果函数返回一个值,它会成为 Promise 的 resolved 值
3. 如果函数抛出错误,它会成为 Promise 的 rejected 值
async function getNumber() {
return 42; // 等价于 Promise.resolve(42)
}
// 示例 2: 返回 Promise
async function getUser() {
return fetch(‘/api/user’); // 返回一个 Promise
}
// 示例 3: 抛出错误
async function fail() {
throw new Error(“出错了!”); // 等价于 Promise.reject(error)
}
理解 await 表达式
await
关键字只能在 async 函数内部使用,它的作用是暂停 async 函数的执行,等待一个 Promise 完成(resolved 或 rejected),然后继续执行 async 函数。
await 的行为
1. 当遇到 await 时,JavaScript 引擎会暂停 async 函数的执行
2. 等待 await 后面的 Promise 完成
3. 如果 Promise 成功完成(resolved),返回 resolved 的值
4. 如果 Promise 被拒绝(rejected),抛出拒绝的原因(可以使用 try/catch 捕获)
// 等待 fetch 完成,然后将响应赋值给 response
const response = await fetch(‘https://api.example.com/user’);
// 等待解析 JSON 完成,然后将结果赋值给 data
const data = await response.json();
return data;
}
await 执行流程可视化
错误处理
处理 async/await 中的错误有两种主要方式:
方式 1: try/catch 块
try {
const response = await fetch(‘https://api.example.com/data’);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(‘请求失败:’, error);
}
}
方式 2: 使用 catch 方法
const response = await fetch(‘https://api.example.com/data’)
.catch(error => console.error(‘请求失败:’, error));
if (!response) return; // 如果出错则终止
const data = await response.json();
console.log(data);
}
重要提示
1. 如果没有错误处理,async 函数中抛出的错误会导致返回的 Promise 被拒绝
2. 总是处理异步操作中的潜在错误,避免程序崩溃
async/await 最佳实践
1. 避免不必要的顺序等待
如果多个操作之间没有依赖关系,可以并行执行:
async function getSequential() {
const user = await fetchUser();
const posts = await fetchPosts(); // 等待用户数据获取完成后才开始
return { user, posts };
}
// 并行执行 – 快 ✅
async function getParallel() {
const userPromise = fetchUser(); // 立即开始,不等待
const postsPromise = fetchPosts(); // 立即开始,不等待
const user = await userPromise;
const posts = await postsPromise;
return { user, posts };
}
更简洁的方式是使用 Promise.all()
:
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
return { user, posts };
}
2. 在循环中使用 async/await
在循环中使用 await 时需要特别注意执行顺序:
async function processSequentially(items) {
for (const item of items) {
await processItem(item);
}
}
// 并行执行 – 所有操作同时开始
async function processParallel(items) {
const promises = items.map(item => processItem(item));
await Promise.all(promises);
}
Promise vs async/await 对比
return fetch(‘/api/user’)
.then(response => {
if (!response.ok) {
throw new Error(‘网络响应错误’);
}
return response.json();
})
.then(user => {
return fetch(`/api/posts/${user.id}`);
})
.then(response => response.json())
.catch(error => {
console.error(‘请求失败:’, error);
});
}
try {
const response = await fetch(‘/api/user’);
if (!response.ok) {
throw new Error(‘网络响应错误’);
}
const user = await response.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return posts;
} catch (error) {
console.error(‘请求失败:’, error);
}
}
async/await 使异步代码更接近同步代码的写法,大大提高了可读性和可维护性。
总结
1. async 函数:声明一个异步函数,它总是返回一个 Promise
2. await 表达式:暂停 async 函数的执行,等待 Promise 的结果
3. 错误处理:使用 try/catch 或 Promise.catch() 处理错误
4. 并行优化:没有依赖关系的操作应该并行执行
5. 兼容性:现代浏览器和Node.js(8+)都支持 async/await
async/await 并不是替代 Promise,而是在 Promise 之上提供了更简洁的语法糖。
掌握了 async/await,你就拥有了编写干净、高效异步JavaScript代码的能力!
核心要点速查
async 函数
• 用 async 关键字声明
• 总是返回一个 Promise
• 可以使用 await 关键字
await 表达式
• 只能在 async 函数中使用
• 暂停执行直到 Promise 完成
• 返回 Promise 解决的值
错误处理
• try/catch 捕获错误
• 或者在返回的 Promise 上使用 catch
• 未处理的错误会导致 Promise 被拒绝
并行执行
• 没有依赖的操作应并行执行
• 使用 Promise.all() 同时等待多个 Promise
常见用途
• 网络请求 (fetch)
• 文件操作 (Node.js)
• 数据库操作
• 定时器操作
注意事项
• 避免在循环中顺序执行无依赖操作
• 顶层代码中不能直接使用 await
• 在类方法中也可以使用 async