你应该了解的JavaScript async/await模式
异步编程是现代JavaScript的核心部分,而async/await使其可读性大大提高。但许多开发者仍然在与常见陷阱作斗争:未处理的promise拒绝、缓慢的顺序请求或令人困惑的错误处理。在本文中,我们将探讨实用的async/await模式,你可以立即应用它们来编写更简洁、更高效的代码。
1. 基本的async/await语法
async函数总是返回一个Promise。await关键字会暂停执行,直到Promise敲定,然后返回其解析值。如果Promise被拒绝,它会抛出一个错误,你可以用try/catch捕获。
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
}
}
这是基础。但现实世界的场景通常需要更细致的方法。
2. 优雅地处理错误
async/await的最大优势之一是你可以使用try/catch处理同步和异步错误。但是,要注意在单个try块中有多个await调用:如果第一个失败,后续的将不会执行。
为了更好的粒度,你可以将每个await包装在自己的try/catch中,或使用辅助函数来避免重复的try/catch块。
async function getUser(id) {
try {
const user = await fetchUser(id);
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
3. 并行运行promise
顺序等待多个独立的promise是一个常见的性能错误。如果你需要来自多个来源的结果,使用Promise.all()来并发运行它们。
// 顺序:慢
const user = await fetchUser();
const posts = await fetchPosts();
// 并行:快
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
当你想要等待所有promise敲定,无论它们是解析还是拒绝时,使用Promise.allSettled()。当你想要收集结果,即使某些失败时,这很有用。
4. 顺序重要时的顺序执行
有时你需要按顺序处理项目,例如当每个步骤依赖于前一个步骤时。在这种情况下,使用for...of循环配合await,而不是forEach(它不会等待异步回调)。
for (const item of items) {
await processItem(item);
}
如果你需要限制并发,考虑使用像p-limit这样的库或实现一个简单的队列。
5. 避免循环中的async/await陷阱
在Array.prototype.forEach内部使用await是一个经典错误:forEach不会等待异步回调,所以循环在promise解析之前就结束了。始终使用for...of或map配合Promise.all。
6. 带超时的async/await
为了防止挂起的请求,你可以让一个promise与超时竞争。
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
await Promise.race([fetchData(), timeout(5000)]);
这个模式对于可能挂起的API调用很方便。
7. 异步生成器和for await...of
异步生成器允许你异步地产生一系列值。你可以用for await...of来消费它们。
async function* fetchPages() {
let page = 1;
while (true) {
const data = await fetch(`/api/items?page=${page}`);
if (data.length === 0) break;
yield data;
page++;
}
}
for await (const page of fetchPages()) {
console.log(page);
}
这对于分页API或流式数据非常有用。
8. 常见错误及如何避免
- 忘记await:调用async函数而不使用await会返回一个Promise,错误可能未被处理。
- 在非async函数中使用await:语法错误。始终将函数标记为async。
- 过度使用顺序await:尽可能使用Promise.all。
- 不处理拒绝:始终添加.catch或try/catch。
9. 编写简洁异步代码的最佳实践
- 为了可读性,优先使用async/await而不是原始Promise。
- 对独立操作使用Promise.all。
- 在适当的级别处理错误。
- 保持async函数小而专注。
- 在循环中谨慎使用async/await;选择正确的模式。
常见问题
async/await和Promise有什么区别?
async/await是Promise的语法糖,使异步代码看起来像同步代码。它通过try/catch简化了链式调用和错误处理。
我可以在forEach循环中使用await吗?
不,forEach不会等待异步回调。改用for...of或Promise.all配合map。
如何并行运行多个异步操作?
使用Promise.all()并发运行它们并等待所有解析。
处理JSON API时,你可以使用我们的JSON Formatter快速格式化和验证响应。