async/await 用法
Async/Await 用法详解
async
和 await
是现代 JavaScript(ES2017+)引入的语法,用于简化异步编程,基于 Promise 提供更直观的代码结构。它让异步代码看起来像同步代码,提高可读性和维护性。本指南将详细讲解 async/await
的用法、场景、注意事项,并提供清晰示例。以下内容假设您熟悉 JavaScript 和 Promise。
1. 基本概念
async
关键字:- 用于声明异步函数。
async
函数总是返回一个Promise
对象(即使返回值不是 Promise,会自动包装为 resolved 的 Promise)。- 语法:
async function functionName() { ... }
。 await
关键字:- 只能在
async
函数内使用。 - 用于暂停执行,等待 Promise 解析(resolve)或拒绝(reject)。
await
后接 Promise 对象,返回其 resolved 值。
核心机制:async/await
是 Promise 的语法糖,简化 .then()
和 .catch()
的链式调用。
2. 基本用法
2.1 简单示例
以下是一个使用 async/await
模拟异步请求的例子:
// 模拟异步函数(返回 Promise)
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data fetched!"), 1000);
});
}
// 异步函数
async function getData() {
console.log("Starting...");
const result = await fetchData(); // 等待 Promise 解析
console.log(result); // 输出 "Data fetched!"
console.log("Finished!");
}
// 调用
getData();
输出(1秒后):
Starting...
Data fetched!
Finished!
解释:
async function getData()
声明异步函数。await fetchData()
暂停执行,直到fetchData
的 Promise 解析。result
接收 Promise 的 resolved 值(”Data fetched!”)。
2.2 与 Promise 对比
使用 .then()
的写法:
function getDataWithPromise() {
console.log("Starting...");
fetchData()
.then(result => {
console.log(result);
console.log("Finished!");
});
}
async/await
使代码更像同步逻辑,减少嵌套,提高可读性。
3. 错误处理
await
的 Promise 可能被拒绝(reject),需要用 try...catch
捕获错误。
function fetchDataWithError() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Fetch failed!")), 1000);
});
}
async function getData() {
try {
console.log("Starting...");
const result = await fetchDataWithError();
console.log(result); // 不会执行
} catch (error) {
console.error("Error:", error.message); // 输出 "Error: Fetch failed!"
}
console.log("Finished!");
}
getData();
输出(1秒后):
Starting...
Error: Fetch failed!
Finished!
说明:
try
块包含await
语句,捕获可能的错误。catch
块处理 Promise 的 reject 状态。- 等价于
.catch(error => console.error(error))
。
4. 高级用法
4.1 串行执行(Sequential)
多个异步操作按顺序执行:
async function fetchMultiple() {
try {
const data1 = await fetchData("Data 1");
console.log(data1);
const data2 = await fetchData("Data 2");
console.log(data2);
} catch (error) {
console.error("Error:", error.message);
}
}
function fetchData(value) {
return new Promise(resolve => setTimeout(() => resolve(value), 1000));
}
fetchMultiple();
输出(每秒输出一行):
Data 1
Data 2
说明:await
使异步调用按顺序执行,第二个 await
等待第一个完成。
4.2 并行执行(Parallel)
使用 Promise.all
同时执行多个异步操作:
async function fetchParallel() {
try {
const [data1, data2] = await Promise.all([
fetchData("Data 1"),
fetchData("Data 2")
]);
console.log(data1, data2);
} catch (error) {
console.error("Error:", error.message);
}
}
fetchParallel();
输出(1秒后同时输出):
Data 1 Data 2
说明:
Promise.all
接受一个 Promise 数组,返回所有 Promise 的 resolved 值数组。- 比串行更快,因为两个
fetchData
同时执行。
4.3 结合循环
在循环中使用 await
:
async function processItems(items) {
for (const item of items) {
const result = await fetchData(item);
console.log(result);
}
}
processItems(["Item 1", "Item 2"]);
输出(每秒输出一行):
Item 1
Item 2
并行循环(更快):
async function processItemsParallel(items) {
const promises = items.map(item => fetchData(item));
const results = await Promise.all(promises);
results.forEach(result => console.log(result));
}
5. 实际应用场景
5.1 HTTP 请求
使用 fetch
API 获取数据:
async function fetchUser(userId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) throw new Error("Network error");
const user = await response.json();
console.log("User:", user.name);
} catch (error) {
console.error("Error:", error.message);
}
}
fetchUser(1);
输出:User: Leanne Graham
(假设 API 返回该数据)。
5.2 超时处理
结合 setTimeout
实现超时:
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), ms);
});
}
async function fetchWithTimeout() {
try {
const result = await Promise.race([
fetchData("Data"),
timeout(500) // 500ms 超时
]);
console.log(result);
} catch (error) {
console.error("Error:", error.message);
}
}
fetchWithTimeout();
输出(如果 fetchData
超过 500ms):Error: Timeout
。
6. 好处
- 可读性强:
- 代码接近同步写法,避免
.then
嵌套(回调地狱)。 - 逻辑清晰,易于维护。
- 错误处理简洁:
try...catch
统一处理同步和异步错误。- 比
.catch
链更直观。
- 灵活性:
- 支持串行、并行、循环等多种异步模式。
- 兼容所有 Promise-based API(如
fetch
、Node.js 异步函数)。
7. 注意事项
- 只能在
async
函数内使用await
:
- 在普通函数中使用
await
会抛出语法错误。 - 顶层
await
(Top-level await)仅在 ES Modules 或特定环境(如 Node.js REPL)支持。
- 性能考虑:
- 串行
await
可能导致等待时间累加,必要时用Promise.all
。 - 避免在循环中逐个
await
,考虑并行处理。
- 错误处理:
- 未捕获的错误会导致 Promise 拒绝,需用
try...catch
。 Promise.all
中任一 Promise 拒绝,整个操作失败(可用Promise.allSettled
替代)。
- 兼容性:
async/await
在现代浏览器和 Node.js(v7.6+)广泛支持。- 老旧环境需 Babel 转译。
8. 常见问题与故障排除
问题 | 原因 | 解决方案 |
---|---|---|
SyntaxError: await is only valid in async function | 在非 async 函数中使用 await | 确保函数声明为 async 。 |
异步操作顺序错误 | 过多串行 await | 使用 Promise.all 并行执行。 |
未捕获错误导致程序崩溃 | 缺少 try...catch | 始终用 try...catch 包裹 await 。 |
await 后返回 Promise | 误认为 await 返回非 Promise 值 | await 总是解析 Promise 的 resolved 值。 |
9. 高级示例:链式异步操作
模拟用户数据和帖子加载:
async function fetchUserAndPosts(userId) {
try {
// 获取用户
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!userResponse.ok) throw new Error("Failed to fetch user");
const user = await userResponse.json();
console.log("User:", user.name);
// 获取用户帖子
const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
if (!postsResponse.ok) throw new Error("Failed to fetch posts");
const posts = await postsResponse.json();
console.log("Posts count:", posts.length);
return { user, posts };
} catch (error) {
console.error("Error:", error.message);
throw error; // 可选择抛出错误供调用者处理
}
}
async function main() {
const result = await fetchUserAndPosts(1);
console.log("Result:", result);
}
main();
输出:
User: Leanne Graham
Posts count: 10
Result: { user: {...}, posts: [...] }
说明:展示了 async/await
在真实 API 调用中的链式操作和错误处理。
10. 总结
- 定义:
async
声明异步函数,返回 Promise;await
暂停执行,解析 Promise。 - 用法:简化异步代码,支持串行、并行、循环、错误处理。
- 好处:可读性强、错误处理简洁、灵活性高。
- 场景:HTTP 请求、文件操作、数据库查询等。
- 注意:需在
async
函数内使用,合理管理串行/并行,始终处理错误。
如果您需要特定场景的 async/await
示例(如 Node.js 文件操作、数据库查询)或想深入某方面(如性能优化),请告诉我,我可以进一步定制代码!