Node.js 把 JavaScript 带出浏览器,靠的是单线程事件循环加非阻塞 I/O。下面这几段是从零跑一个服务、读写文件、再到用流处理大文件时最常用的骨架。

1. 最小 HTTP 服务

import { createServer } from 'node:http';

const server = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('霍州比特人:hello from node
');
});

server.listen(3000, () => console.log('http://localhost:3000'));

2. 事件循环的顺序

同步代码先跑完,接着是微任务(Promise、queueMicrotask),再进入宏任务各阶段(timers、poll、check…)。setImmediate 属于 check 阶段,setTimeout(fn, 0) 属于 timers,两者的先后在主模块里并不总是固定。

console.log('A');
queueMicrotask(() => console.log('B'));
setTimeout(() => console.log('C'), 0);
setImmediate(() => console.log('D'));
Promise.resolve().then(() => console.log('E'));
console.log('F');
// 常见输出:A F B E C D(C / D 在主模块中可能互换)

3. 文件与流

import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { createGzip } from 'node:zlib';

await pipeline(
  createReadStream('access.log'),
  createGzip(),
  createWriteStream('access.log.gz')
);

pipeline 会替你处理背压和错误传递,比手写 .pipe() 加一堆 error 监听稳得多。大文件永远不要 readFileSync 全读进内存。

4. 模块与常用全局

node --watch server.mjs        # 改动自动重启
node --env-file=.env app.mjs  # 加载环境变量
npm pkg set type=module       # package.json 开启 ESM
  • import.meta.url 配 fileURLToPath 拿当前文件目录,别再用 __dirname 猜。
  • process.env.NODE_ENV 只做分支,不要把密钥写进代码库。
  • CPU 密集任务交给 worker_threads,事件循环不适合硬算。

Node 的难点从来不是语法,是记住「这一行会不会把事件循环堵住」。