什么是 async/await?
async/await 是 ES2017(ES8)引入的处理异步操作的新语法,它是基于 Promise 的语法糖,让异步代码的写法更像同步代码,更加清晰易读。
基本概念
1. async 函数
- 声明一个异步函数
- 自动返回一个 Promise 对象
- 函数内部可以使用
await
// 普通函数
function normalFunc() {
return 'hello';
}
// async 函数
async function asyncFunc() {
return 'hello'; // 自动包装成 Promise
}
// 调用 async 函数
asyncFunc().then(value => console.log(value)); // "hello"
2. await 表达式
- 只能在 async 函数内部使用
- 暂停代码执行,等待 Promise 完成
- 返回 Promise 的结果
async function getData() {
// await 会等待 fetch 完成,然后返回结果
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
基本用法
示例1:基本 async/await
// 模拟异步操作
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function demo() {
console.log('开始');
// 等待1秒
await delay(1000);
console.log('1秒后');
// 再等待2秒
await delay(2000);
console.log('又2秒后');
return '完成';
}
demo().then(result => console.log(result));
// 输出:
// 开始
// (1秒后)
// 1秒后
// (2秒后)
// 又2秒后
// 完成
示例2:处理 HTTP 请求
async function fetchUserData(userId) {
try {
// 等待第一个请求完成
const userResponse = await fetch(`/api/users/${userId}`);
const user = await userResponse.json();
// 使用第一个结果发起第二个请求
const postsResponse = await fetch(`/api/users/${userId}/posts`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('获取数据失败:', error);
throw error;
}
}
// 使用
fetchUserData(123)
.then(data => console.log('用户数据:', data))
.catch(error => console.error('错误:', error));
错误处理
1. try...catch(推荐)
async function riskyOperation() {
try {
const result = await someAsyncFunction();
const anotherResult = await anotherAsyncFunction(result);
return anotherResult;
} catch (error) {
// 捕获所有 await 抛出的错误
console.error('操作失败:', error);
// 可以返回默认值
return { default: 'value' };
// 或者重新抛出
// throw new Error('包装后的错误');
} finally {
console.log('无论成功失败都会执行');
}
}
2. catch() 方法
async function example() {
const result = await someAsyncFunction()
.catch(error => {
console.error('捕获错误:', error);
return '默认值'; // 提供降级值
});
console.log('结果:', result);
}
实际应用场景
场景1:顺序执行异步操作
// 之前使用 Promise.then 的链式调用
function oldWay() {
fetchUser()
.then(user => fetchOrders(user.id))
.then(orders => fetchProducts(orders[0].id))
.then(product => console.log(product))
.catch(error => console.error(error));
}
// 使用 async/await 更清晰
async function newWay() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
const product = await fetchProducts(orders[0].id);
console.log(product);
} catch (error) {
console.error(error);
}
}
场景2:并行执行异步操作
async function parallelOperations() {
// 并行执行,互不依赖
const [user, products, cart] = await Promise.all([
fetchUser(),
fetchProducts(),
fetchCart()
]);
// 所有数据都获取后再处理
return { user, products, cart };
}
// 错误处理的并行操作
async function parallelWithErrorHandling() {
try {
const [userResult, productResult] = await Promise.allSettled([
fetchUser(),
fetchProducts()
]);
const user = userResult.status === 'fulfilled'
? userResult.value
: null;
const products = productResult.status === 'fulfilled'
? productResult.value
: [];
return { user, products };
} catch (error) {
console.error('其他错误:', error);
}
}
场景3:循环中的异步操作
// ❌ 错误的方式 - 没有正确使用 await
async function wrongLoop() {
const urls = ['url1', 'url2', 'url3'];
const results = [];
urls.forEach(async (url) => {
const data = await fetch(url); // 这不会按顺序执行
results.push(data);
});
console.log(results); // 这里可能还是空的
}
// ✅ 正确的方式1 - 顺序执行
async function sequentialLoop() {
const urls = ['url1', 'url2', 'url3'];
const results = [];
for (const url of urls) {
const data = await fetch(url); // 等待一个完成再下一个
results.push(data);
}
return results;
}
// ✅ 正确的方式2 - 并行执行
async function parallelLoop() {
const urls = ['url1', 'url2', 'url3'];
// 所有请求并行执行
const promises = urls.map(url => fetch(url));
const results = await Promise.all(promises);
return results;
}
高级用法
1. 立即执行 async 函数
// IIFE 模式(立即调用的函数表达式)
(async function() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
})();
// 箭头函数版本
(async () => {
const result = await someAsyncTask();
console.log(result);
})();
2. 在类中使用 async 方法
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async get(endpoint) {
const response = await fetch(`${this.baseUrl}/${endpoint}`);
return response.json();
}
async post(endpoint, data) {
const response = await fetch(`${this.baseUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
}
// 使用
const client = new ApiClient('https://api.example.com');
const users = await client.get('users');
3. 多个错误处理策略
async function complexOperation() {
// 尝试主方法
let data = await fetchPrimary()
.catch(() => null); // 失败返回 null
// 如果主方法失败,尝试备用方法
if (!data) {
data = await fetchBackup()
.catch(error => {
console.warn('备用方法也失败:', error);
return getDefaultData(); // 返回默认数据
});
}
// 处理数据,可能抛出错误
try {
return processData(data);
} catch (error) {
// 处理处理过程中的错误
return handleProcessingError(error);
}
}
注意事项和最佳实践
1. 不要滥用 await
// ❌ 不好的写法 - 不必要的串行
async function badPractice() {
const user = await getUser(); // 等待
const orders = await getOrders(); // 等待
const settings = await getSettings(); // 等待
// 这三个请求是独立的,但被串行执行了
}
// ✅ 好的写法 - 并行执行
async function goodPractice() {
const [user, orders, settings] = await Promise.all([
getUser(),
getOrders(),
getSettings()
]);
// 三个请求并行执行
}
2. 错误处理要全面
async function safeFunction() {
try {
// 主要逻辑
const result = await mainOperation();
// 可能抛出错误的后续操作
const processed = processResult(result);
return processed;
} catch (error) {
// 根据错误类型处理
if (error instanceof NetworkError) {
return retryOperation();
} else if (error instanceof ValidationError) {
return getDefaultValue();
} else {
// 记录并重新抛出未知错误
logError(error);
throw error;
}
}
}
3. async/await 和 Promise 混合使用
async function mixedUsage() {
// 在 async 函数中也可以使用 .then/.catch
const data = await fetchData()
.then(response => transformData(response))
.catch(error => {
console.error('获取数据失败:', error);
return getDefaultData();
});
// 也可以返回新的 Promise
return new Promise((resolve, reject) => {
// 一些回调式的异步操作
someCallbackFunction((err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
常见问题解答
Q1: async 函数总是返回 Promise 吗?
是的,即使你返回一个普通值,也会被自动包装成 Promise。
Q2: await 只能在 async 函数中使用吗?
是的,在普通函数中使用会报语法错误。
Q3: 顶层 await 可以用吗?
在 ES2022+ 中可用,但在模块中需要特定配置:
// 在模块中可以直接使用
const response = await fetch('/api/data');
Q4: async/await 会阻塞线程吗?
不会,await 只是暂停 async 函数的执行,不会阻塞 JavaScript 主线程。
总结
async/await 的优点:
- 代码更清晰:像写同步代码一样写异步代码
- 错误处理更简单:可以使用 try...catch
- 调试更方便:错误堆栈更清晰
- 条件判断更直观:可以在 if、for 等语句中直接使用
适用场景:
- 多个有依赖关系的异步操作
- 需要复杂错误处理的异步流程
- 希望代码更易读和维护的项目
async/await 并没有取代 Promise,而是让 Promise 的使用更加优雅。两者结合使用可以写出更健壮的异步代码。