Node.js 异步编程
异步是 Node 的地基:三种写法、错误怎么传、什么时候会丢。
Key Points回调是原始形态,Promise 让它可组合,async/await 让它看起来像同步。
三种写法
JavaScript
const fs = require('node:fs/promises');
// 1. 回调
fs.readFile('a.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log('回调:', data.length);
});
// 2. Promise
fs.readFile('a.txt', 'utf8')
.then((data) => console.log('Promise:', data.length))
.catch((err) => console.error(err));
// 3. async/await
(async () => {
try {
const data = await fs.readFile('a.txt', 'utf8');
console.log('await:', data.length);
} catch (err) {
console.error(err);
}
})();并发不要写成串行
JavaScript
const fs = require('node:fs/promises');
const files = ['a.txt', 'b.txt', 'c.txt'];
// 慢:一个读完才读下一个
async function serial() {
const t = Date.now();
const out = [];
for (const f of files) out.push(await fs.readFile(f, 'utf8'));
console.log('串行', Date.now() - t, 'ms');
return out;
}
// 快:同时发起
async function parallel() {
const t = Date.now();
const out = await Promise.all(files.map((f) => fs.readFile(f, 'utf8')));
console.log('并发', Date.now() - t, 'ms');
return out;
}Output
串行 36 ms
并发 13 msawait inside a for waits one by one; Promise.all fires them all. Three 12 ms reads take 36 ms serially and 13 ms concurrently — the gap grows linearly.
错误怎么传
| Style | How errors arrive |
|---|---|
| 回调 | 第一个参数 err |
| Promise | .catch() 或 reject |
| async/await | try/catch |
| EventEmitter | 'error' 事件 |
An exception thrown inside an
async function becomes a rejected Promise. Unhandled, Node 22 crashes the process with UnhandledPromiseRejection. That is by design — silent failures are worse.别在循环里忘掉 await
JavaScript
// 坑:forEach 不认 await
[1, 2, 3].forEach(async (n) => {
await sleep(n * 100);
console.log(n); // 顺序不可控,外层也不等它
});
// 对:for...of 或 Promise.all
for (const n of [1, 2, 3]) {
await sleep(n * 100);
console.log(n); // 1 2 3 严格有序
}三种写法
JavaScript
const fs = require('node:fs/promises');
// 1. 回调
fs.readFile('a.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log('回调:', data.length);
});
// 2. Promise
fs.readFile('a.txt', 'utf8')
.then((data) => console.log('Promise:', data.length))
.catch((err) => console.error(err));
// 3. async/await
(async () => {
try {
const data = await fs.readFile('a.txt', 'utf8');
console.log('await:', data.length);
} catch (err) {
console.error(err);
}
})();并发不要写成串行
JavaScript
const fs = require('node:fs/promises');
const files = ['a.txt', 'b.txt', 'c.txt'];
// 慢:一个读完才读下一个
async function serial() {
const t = Date.now();
const out = [];
for (const f of files) out.push(await fs.readFile(f, 'utf8'));
console.log('串行', Date.now() - t, 'ms');
return out;
}
// 快:同时发起
async function parallel() {
const t = Date.now();
const out = await Promise.all(files.map((f) => fs.readFile(f, 'utf8')));
console.log('并发', Date.now() - t, 'ms');
return out;
}Output
串行 36 ms
并发 13 msfor 里 await 会一个一个等,Promise.all 一次全发出去。三个文件各自 12ms,串行 36ms,并发 13ms —— 差距随文件数线性放大。
错误怎么传
| 写法 | 错误如何到达 |
|---|---|
| 回调 | 第一个参数 err |
| Promise | .catch() 或 reject |
| async/await | try/catch |
| EventEmitter | 'error' 事件 |
async 函数里抛出的异常会变成 rejected Promise。如果没人 catch,Node 22 默认让进程崩溃并打印 UnhandledPromiseRejection。这不是 bug,是设计——静默失败更难查。别在循环里忘掉 await
JavaScript
// 坑:forEach 不认 await
[1, 2, 3].forEach(async (n) => {
await sleep(n * 100);
console.log(n); // 顺序不可控,外层也不等它
});
// 对:for...of 或 Promise.all
for (const n of [1, 2, 3]) {
await sleep(n * 100);
console.log(n); // 1 2 3 严格有序
}