Node.js 异步编程

异步是 Node 的地基:三种写法、错误怎么传、什么时候会丢。

Intermediate
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 ms

await 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.

错误怎么传

StyleHow errors arrive
回调第一个参数 err
Promise.catch() 或 reject
async/awaittry/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 严格有序
}

Friends & Partners