异步编程的 callback hell 到 async/await 演进
FreeGuideOnline
最新
2026-07-10
javascript console.log('开始');
setTimeout(() => { console.log('2 秒后执行的回调'); }, 2000);
console.log('结束'); // 输出顺序:开始 → 结束 → 2 秒后执行的回调
在 Node.js 中,读取文件也经常用回调:
```javascript
const fs = require('fs');
fs.readFile('data.txt', 'utf-8', (err, data) => {
if (err) {
console.error('读取失败', err);
return;
}
console.log('文件内容:', data);
});
回调模式简单直接,但如果多个异步操作需要按顺序执行,并且后一个依赖前一个的结果,代码就会迅速失控。
回调地狱(Callback Hell)
假设需要完成三个连续操作:读取文件 → 根据内容查询数据库 → 将结果写入新文件。使用回调会写成这样:
fs.readFile('config.json', 'utf-8', (err, configStr) => {
if (err) {
console.error('读配置失败', err);
return;
}
const config = JSON.parse(configStr);
db.query('SELECT * FROM users WHERE id = ?', [config.userId], (err, users) => {
if (err) {
console.error('查询失败', err);
return;
}
const user = users[0];
fs.writeFile('result.txt', JSON.stringify(user), (err) => {
if (err) {
console.error('写入失败', err);
return;
}
console.log('全部完成');
});
});
});
这就是经典的回调地狱:
- 代码层层嵌套,形成向右的“金字塔”,可读性极差。
- 错误处理重复,每个回调都要判断
err,容易遗漏。 - 流程逻辑不直观,难以维护和调试。
解救者:Promise
ES6 引入了 Promise,专门用来组织异步代码。Promise 表示一个异步操作的最终完成(或失败)及其结果值,它有三种状态:
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
状态一旦改变就不可逆。通过 .then() 处理成功,.catch() 处理失败,链式调用可以彻底拉平嵌套。
用 Promise 改写上述例子。先将每个异步操作包装成返回 Promise 的函数:
function readFilePromise(path, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
function queryPromise(sql, params) {
return new Promise((resolve, reject) => {
db.query(sql, params, (err, results) => {
if (err) reject(err);
else resolve(results);
});
});
}
function writeFilePromise(path, data) {
return new Promise((resolve, reject) => {
fs.writeFile(path, data, (err) => {
if (err) reject(err);
else resolve();
});
});
}
然后串联调用:
readFilePromise('config.json', 'utf-8')
.then(configStr => {
const config = JSON.parse(configStr);
return queryPromise('SELECT * FROM users WHERE id = ?', [config.userId]);
})
.then(users => {
const user = users[0];
return writeFilePromise('result.txt', JSON.stringify(user));
})
.then(() => {
console.log('全部完成');
})
.catch(err => {
console.error('流程出错', err);
});
对比回调地狱,优劣立判:
- 代码缩进保持在同一层级,逻辑一目了然。
- 错误统一在最后的
.catch()处理,避免遗漏。 .then()链可以将异步步骤清晰的串联起来。
更优雅的终极方案:async/await
ES2017 带来了 async/await,它基于 Promise,但提供了近乎同步代码的书写体验,彻底消灭了回调痕迹。
async:声明一个异步函数,函数内部可以使用await,并自动将返回值包装成 Promise。await:等待一个 Promise 完成(fulfilled 或 rejected),并取出其结果。只能在 async 函数内部使用。如果 Promise 被 reject,会抛出异常,可以用try...catch捕获。
重写相同流程,代码变得非常直白:
async function processData() {
try {
const configStr = await readFilePromise('config.json', 'utf-8');
const config = JSON.parse(configStr);
const users = await queryPromise('SELECT * FROM users WHERE id = ?', [config.userId]);
const user = users[0];
await writeFilePromise('result.txt', JSON.stringify(user));
console.log('全部完成');
} catch (err) {
console.error('流程出错', err);
}
}
processData();