JavaScript async/await
在讲解 async/await 之前,我们需要先理解 JavaScript 中的异步编程概念,可以参考:JavaScript 异步编程。
JavaScript 是单线程语言,意味着它一次只能执行一个任务。为了避免长时间运行的任务阻塞主线程,JavaScript 使用异步编程模型。
异步 vs 同步
- 同步(Synchronous)编程:代码按顺序执行,前一个操作完成后才会执行下一个
- 异步编程(Asynchronous):某些操作被放入"任务队列",主线程继续执行后续代码,等主线程空闲时再处理队列中的任务
实例
// 同步示例
console.log('1');
console.log('2');
// 输出: 1, 2
// 异步示例
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出: 1, 3, 2
console.log('1');
console.log('2');
// 输出: 1, 2
// 异步示例
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出: 1, 3, 2
回调函数的问题
在 async/await 出现之前,JavaScript 主要使用回调函数处理异步操作,但这会导致"回调地狱"(Callback Hell)。
实例
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
console.log(d);
});
});
});
});
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
console.log(d);
});
});
});
});
这种嵌套结构使得代码难以阅读和维护。
Promise 的引入
ES6 引入了 Promise 对象来解决回调地狱问题。
实例
function getData() {
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => resolve('数据'), 1000);
});
}
getData()
.then(data => {
console.log(data);
return getMoreData(data);
})
.then(moreData => {
console.log(moreData);
})
.catch(error => {
console.error(error);
});
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => resolve('数据'), 1000);
});
}
getData()
.then(data => {
console.log(data);
return getMoreData(data);
})
.then(moreData => {
console.log(moreData);
})
.catch(error => {
console.error(error);
});
虽然 Promise 改善了回调问题,但 then() 链式调用仍然不够直观。
async/await 语法
ES2017 引入了 async/await,它建立在 Promise 之上,让异步代码看起来像同步代码一样。
async 函数
在函数声明前添加 async 关键字,表示该函数是异步的:
async function fetchData() {
// 函数体
}
// 函数体
}
async 函数总是返回一个 Promise:
- 如果返回值不是 Promise,会自动包装成 resolved Promise
- 如果抛出异常,会返回 rejected Promise
await 表达式
await 只能在 async 函数内部使用:
async function fetchData() {
const result = await somePromise;
console.log(result);
}
const result = await somePromise;
console.log(result);
}
await 会暂停 async 函数的执行,等待 Promise 完成:
- 如果 Promise 被 resolve,返回 resolve 的值
- 如果 Promise 被 reject,抛出错误(可以用 try/catch 捕获)
实际应用示例
基本用法
实例
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function showMessage() {
console.log('开始');
await delay(1000);
console.log('1秒后');
await delay(1000);
console.log('又1秒后');
}
showMessage();
return new Promise(resolve => setTimeout(resolve, ms));
}
async function showMessage() {
console.log('开始');
await delay(1000);
console.log('1秒后');
await delay(1000);
console.log('又1秒后');
}
showMessage();
错误处理
实例
async function fetchUserData() {
try {
const response = await fetch('https://api.example.com/user');
if (!response.ok) {
throw new Error('网络响应不正常');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('获取数据失败:', error);
}
}
try {
const response = await fetch('https://api.example.com/user');
if (!response.ok) {
throw new Error('网络响应不正常');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('获取数据失败:', error);
}
}
并行执行
如果需要并行执行多个异步操作,可以使用 Promise.all:
实例
async function fetchMultipleData() {
const [userData, productData] = await Promise.all([
fetch('/api/user'),
fetch('/api/products')
]);
const user = await userData.json();
const products = await productData.json();
return { user, products };
}
const [userData, productData] = await Promise.all([
fetch('/api/user'),
fetch('/api/products')
]);
const user = await userData.json();
const products = await productData.json();
return { user, products };
}
常见问题与最佳实践
1. 不要忘记 await
实例
// 错误示例 - 忘记 await
async function example() {
const data = fetch('/api'); // 缺少 await
console.log(data); // 输出 Promise 对象
}
// 正确示例
async function example() {
const data = await fetch('/api');
console.log(data); // 输出实际数据
}
async function example() {
const data = fetch('/api'); // 缺少 await
console.log(data); // 输出 Promise 对象
}
// 正确示例
async function example() {
const data = await fetch('/api');
console.log(data); // 输出实际数据
}
2. 避免不必要的 async
实例
// 不必要 - 函数内部没有 await
async function unnecessaryAsync() {
return 42;
}
// 更简单的写法
function simpleFunction() {
return 42;
}
async function unnecessaryAsync() {
return 42;
}
// 更简单的写法
function simpleFunction() {
return 42;
}
3. 顶层 await
在模块顶层可以直接使用 await(ES2022 特性):
实例
// 在模块中
const data = await fetch('/api');
console.log(data);
const data = await fetch('/api');
console.log(data);
4. 性能考虑
- 顺序执行 vs 并行执行:合理使用 Promise.all 提高性能
- 错误处理:确保所有可能的错误都被捕获
async/await 与传统 Promise 对比
特性 | async/await | Promise then/catch |
---|---|---|
可读性 | 高,类似同步代码 | 中,链式调用 |
错误处理 | 使用 try/catch | 使用 .catch() |
调试 | 更容易,有明确的调用栈 | 较困难,调用栈可能不清晰 |
代码结构 | 更扁平 | 嵌套或链式 |
总结
async/await 是 JavaScript 异步编程的重大改进,它:
- 使异步代码更易读、更易维护
- 基于 Promise,与现有 Promise 代码兼容
- 提供了更直观的错误处理方式
- 改善了调试体验
虽然 async/await 不是完全替代 Promise,但在大多数情况下,它提供了更优雅的解决方案。理解 async/await 的工作原理和最佳实践,将大大提升你的 JavaScript 异步编程能力。