一、为什么需要 async/await?
在 JavaScript 的异步演进史中,我们经历了:
- 回调函数(Callback) :嵌套深、难维护("回调地狱")
- Promise(ES6) :链式调用,但
.then().catch()仍显冗长 - async/await(ES2017 / ES8) :以同步写法处理异步逻辑,代码更清晰、易读、易调试
✅ 核心价值:让异步代码看起来像同步代码,大幅提升可读性与可维护性。
二、async/await 基础语法
1. async 函数
- 在函数前加
async,该函数自动返回一个 Promise - 即使
return一个普通值,也会被包装成Promise.resolve(value)
csharp
javascript
编辑
async function hello() {
return "world";
}
// 等价于:
// function hello() {
// return Promise.resolve("world");
// }
2. await 表达式
- 只能在
async函数内部使用 - 暂停函数执行 ,等待右侧的 Promise fulfilled,并获取其结果
- 若 Promise 被 reject,会抛出异常(可用
try/catch捕获)
ini
javascript
编辑
const data = await fetch('/api/user');
const json = await data.json(); // 继续等待解析
三、实战案例:前端 + Node.js 双场景
场景 1:前端 ------ 获取 GitHub 用户仓库(浏览器环境)
xml
html
预览
<!-- index.html -->
<div id="repos"></div>
<script>
const loadRepos = async () => {
try {
const res = await fetch('https://api.github.com/users/shunwuyu/repos');
if (!res.ok) throw new Error(`状态码: ${res.status}`);
const repos = await res.json();
const html = repos.map(repo => `
<div class="repo">
<h3><a href="${repo.html_url}">${repo.name}</a></h3>
<p>${repo.description || '暂无描述'}</p>
</div>
`).join('');
document.getElementById('repos').innerHTML = html;
} catch (err) {
console.error('加载失败:', err);
document.getElementById('repos').innerHTML = `<p>❌ ${err.message}</p>`;
}
};
loadRepos();
</script>
✅ 优势 :逻辑线性,错误集中处理,无需 .then().catch() 链。
场景 2:Node.js ------ 读取本地 HTML 文件
javascript
javascript
编辑
// readHtml.js
import fs from 'fs';
import { promisify } from 'util';
// 方式1:手动封装 Promise
const readFileAsync = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
err ? reject(err) : resolve(data);
});
});
};
// 方式2(推荐):使用 util.promisify
const readFile = promisify(fs.readFile);
const main = async () => {
try {
const html = await readFile('./index.html', 'utf8');
console.log('文件内容长度:', html.length);
} catch (err) {
console.error('读取失败:', err.message);
}
};
main();
💡 提示 :Node.js 内置模块大多支持
promisify,避免重复造轮子。
四、常见误区与注意事项
❌ 误区1:在非 async 函数中使用 await
vbnet
javascript
编辑
// 报错!SyntaxError: await is only valid in async function
const data = await fetch(...);
❌ 误区2:忽略错误处理
csharp
javascript
编辑
// 危险!若 fetch 失败,程序会崩溃
const res = await fetch(url);
const data = await res.json(); // 可能 never reached
✅ 正确做法 :始终用 try/catch 包裹,或在调用处 .catch()
❌ 误区3:串行执行本可并行的任务
javascript
javascript
编辑
// 低效:串行(总耗时 ≈ 2s)
const user = await fetch('/user');
const posts = await fetch('/posts');
// 高效:并行(总耗时 ≈ 1s)
const [userRes, postsRes] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
const [user, posts] = await Promise.all([
userRes.json(),
postsRes.json()
]);
五、async/await vs Promise:如何选择?
| 特性 | Promise | async/await |
|---|---|---|
| 可读性 | 中等(链式) | 高(类同步) |
| 错误处理 | .catch() 分散 |
try/catch 集中 |
| 并行控制 | Promise.all() 直观 |
需配合 Promise.all() |
| 调试体验 | 断点困难 | 支持逐行调试 |
| 兼容性 | ES6+ | ES2017+(现代环境基本全覆盖) |
✅ 建议 :新项目优先使用
async/await,复杂并行逻辑辅以Promise.all/Promise.race。
六、总结要点
async函数返回 Promise,await等待 Promise 结果- 必须在
async函数内使用await - 永远不要忽略错误处理 :用
try/catch或顶层.catch() - 并行任务用
Promise.all()提升性能 - Node.js 中善用
util.promisify封装回调 API
七、拓展思考
- 如何在不支持
async/await的老浏览器中使用?→ Babel 编译 await后面如果不是 Promise 会怎样?→ 自动包装为Promise.resolve(value)- 能否在顶层(Top-level)使用
await?→ 可以! (ES2022 支持,Node.js 14.8+ / 现代浏览器)
🌟 终极口诀 :
async 标函数,await 等结果;
错误要捕获,并行用 all。