历史背景
Node 早期所有异步都用回调:
const fs = require('fs');
fs.readFile('config.json', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
"Error-first" 约定(Node 风格)
function asyncOp(arg, callback) {
// 完成后调用 callback(err, result)
if (somethingWrong) {
return callback(new Error('Failed')); // ★ 第一个参数永远是 error
}
callback(null, result); // 成功时 err 是 null
}
调用方:
asyncOp(arg, (err, result) => {
if (err) {
console.error(err);
return;
}
// 用 result
});
Callback Hell
嵌套深 → 难读、难错误处理:
fs.readFile('a.json', (err, dataA) => {
if (err) return console.error(err);
fs.readFile('b.json', (err, dataB) => {
if (err) return console.error(err);
fs.writeFile('out.json', dataA + dataB, (err) => {
if (err) return console.error(err);
console.log('Done');
});
});
});
这是经典"末日金字塔"——也是 Promise 诞生的原因。
现代用法:基本不直接写回调了
// 老式回调
fs.readFile('a.json', 'utf8', (err, data) => { ... });
// 现代 Promise(推荐)
import { readFile } from 'fs/promises';
const data = await readFile('a.json', 'utf8');
把回调 API 转 Promise
import { promisify } from 'util';
import fs from 'fs';
const readFile = promisify(fs.readFile);
const data = await readFile('a.json', 'utf8');
或自己包:
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
EventEmitter 模式(另一种"回调")
import { EventEmitter } from 'events';
const ee = new EventEmitter();
ee.on('data', chunk => console.log('Got:', chunk));
ee.emit('data', 'hello');
详见 20-events。
何时还需要懂回调
- 维护老项目
- 看 Node 早期文档
- 一些库(如
http.createServer((req, res) => ...))仍是回调风格 - Stream / EventEmitter 是回调演化
但新代码几乎不直接写 Node 风格回调——promisify / async-await 已经全面取代。
坑
- 调 callback 后别忘 return——不然代码继续往下跑
- 同一个 callback 被调多次——bug 来源(Promise 不会)
- 嵌套深时 try-catch 不能跨回调——错误必须在每层手动传
下一篇:Promise。