
你用 Node.js 写脚本,第一道坎往往就是“怎么把磁盘上的文件读进来”。Node.js 读取文件到底有哪几种方法?答案是四条主路径:同步的 readFileSync、回调式的 readFile、Promise 化的 fs.promises.readFile,以及应对大文件的 createReadStream。今天这篇文章,编程狮就换一个“避坑”的角度,把这四种写法连同最容易踩的坑一次讲透,让你选得对、写得稳。读完你会拿到一张选型表,也清楚每种写法在哪些场景下会翻车。
Node.js 基于事件循环,文件操作天然有“阻塞”和“非阻塞”两条路。选错方式,轻则代码难看,重则高并发时整个服务卡死。所以读文件这件事,重点不在“能不能读出来”,而在“用哪种方式读才不出事”。下面先把结论摆出来,再逐方法拆解。
先看结论
Node.js 读取文件本质是按“要不要阻塞主线程、文件有多大”两条线来选。先对号入座:
| 你的场景 | 推荐方式 | 一句话理由 |
|---|---|---|
| 读小配置文件、一次性脚本 | fs.readFileSync |
代码最短,同步拿到结果 |
| 想保持异步又不想嵌套回调 | fs.promises.readFile + async/await |
现代代码主流写法 |
| 兼容老代码、习惯回调 | fs.readFile |
回调里拿 err 和 data |
| 文件很大或要边读边处理 | fs.createReadStream |
不占满内存,按块流动 |

如果你还没装好 Node.js 环境,建议先过一遍 Node.js 教程,把 fs 模块和模块引入方式先铺平,后面读代码会更顺。
一、方法一:fs.readFileSync 同步写法
fs.readFileSync 是 fs 模块里最直接的一个方法,它会阻塞当前线程直到文件读完,然后把内容作为返回值交给你。语法上不需要回调,也不返回 Promise,直接用变量接住就行。它最适合启动阶段的配置加载、CLI 工具这类“本来就不在请求链路里”的场景。
const fs = require('fs');
// 第二个参数是编码,不写则返回 Buffer
try {
const data = fs.readFileSync('w3cschool-config.txt', 'utf8');
console.log('同步读取到的内容:', data);
} catch (err) {
console.error('读取失败:', err.message);
}
上面这段做了三件事:引入 fs 模块、调用 readFileSync 同步拿内容、用 try/catch 兜住异常。读小文件时这是最省心的写法,但代价是“阻塞”——在事件循环里它会卡住整个进程。在 HTTP 请求处理函数里用它,会让后续请求全部排队,高并发场景务必避免。
避坑点:编码参数传 'utf8' 返回字符串,不传则返回 Buffer,很多人忘了转码就直接当字符串处理,结果出现乱码或方法报错。涉及版本差异时标明 Node 版本号,本例基于 Node.js 18 LTS 实测。
二、方法二:fs.readFile 回调写法
fs.readFile 是异步版本,结果通过回调的两个参数返回:第一个是错误对象 err,第二个是文件内容 data。Node.js 约定“错误优先”,所以一定要先判断 err。回调写法的优点是零依赖、和老代码风格一致,缺点是多次读取嵌套时会形成“回调地狱”。
const fs = require('fs');
fs.readFile('w3cschool-config.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err.message);
return;
}
console.log('异步读取到的内容:', data);
});
回调写法的关键是命中错误后及时 return,否则后续逻辑会拿到 undefined 而崩。它在只做一次简单读取、又不想引入 async 语法时最顺手;但如果你要“读完 A 再读 B 再读 C”,嵌套会迅速失控,那时就该换 Promise 写法。和前端常见写法一脉相承,也可顺带复习 JavaScript 教程 里的异步基础。
避坑点:回调里忘记 return,错误分支之后代码继续执行,是常见的隐蔽 bug。把失败处理写成“先判 err、再 return”的固定动作,能避开一大半问题。
三、方法三:fs.promises.readFile 与 async/await
这是目前最推荐的现代写法。fs.promises 把回调式 API 包成了返回 Promise 的版本,配合 async/await 让异步代码读起来像同步代码。它不阻塞事件循环,适合放在服务请求里,也能用 Promise.all 轻松并发读多个文件。
const fs = require('fs').promises;
async function readDemo() {
try {
const data = await fs.readFile('w3cschool-config.txt', 'utf8');
console.log('用 Promise 读取到的内容:', data);
} catch (err) {
console.error('读取失败:', err.message);
}
}
readDemo();
readFileSync 同步返回、fs.readFile 走回调、fs.promises.readFile 走 Promise——三者拿到的是同一份内容,区别只在“等待结果”的方式。需要并发读取多个文件时,用 Promise.all([a, b, c]) 把多个读取并行起来也很自然。写代码时想随时查参数,可翻 Node.js 官方文档看具体 API 签名的说明。
避坑点:await 必须包在 async 函数里,顶层直接 await 在旧版 Node 会报错;并发数太多要自己做限流,否则一次性打开几百个文件会触及系统文件描述符上限。
四、方法四:fs.createReadStream 流式读取
当文件大到无法一次性塞进内存(比如几百 MB 的日志),readFile 系列会直接撑爆内存。此时要用 fs.createReadStream 做 Node.js 文件流式的读取,它按块(chunk)陆续把数据推给你,边读边处理,内存只占一小块缓冲区。
const fs = require('fs');
const stream = fs.createReadStream('w3cschool-big.log', 'utf8');
let content = '';
stream.on('data', (chunk) => {
content += chunk; // 每收到一块就追加
});
stream.on('end', () => {
console.log('流式读取完成,总长度:', content.length);
});
stream.on('error', (err) => {
console.error('读取失败:', err.message);
});
四种方式的横向对比:
| 方式 | 是否阻塞 | 适合文件 | 内存占用 |
|---|---|---|---|
readFileSync |
是 | 小文件 | 一次性载入 |
fs.readFile |
否 | 中小文件 | 一次性载入 |
fs.promises.readFile |
否 | 中小文件 | 一次性载入 |
fs.createReadStream |
否 | 大文件 | 按块流动 |

避坑点:流式读取默认按 Buffer 切块,一行可能被拆成两半,需要按分隔符自己攒行;另外在 'utf8' 模式下多字节字符也可能被切断,处理文本时要用 readline 模块或自己处理边界。
实操清单:前置、操作、验证与失败处理
- 前置(安装/配置):本机已装 Node.js 18 及以上(用
node -v检查),并在项目目录放一个真实的w3cschool-config.txt文本文件作为读取对象。 - 操作(命令/运行):把示例保存为
read-demo.js,终端执行node read-demo.js启动读取。 - 验证(检查/预期):预期输出是文件原文;若出现
ENOENT,说明文件不在当前工作目录,改用绝对路径重试即可。 - 失败处理(失败/修复):读取大文件报内存溢出时,立刻从
readFileSync切换到fs.createReadStream;编码乱码则确认写入和读取都统一用'utf8'。
总结
Node.js 读取文件共有四种主路径:readFileSync 同步最短、fs.readFile 回调最经典、fs.promises.readFile 配合 async/await 是现代主流、createReadStream 专门应对大文件。Node.js 读取文件有哪几种方法,答案就落在“同步还是异步、文件大不大”这两个判断上。选型时先问自己两个问题——文件大不大、能不能阻塞主线程,答案就出来了。
要点带走:
- 小文件、一次性脚本,优先用
readFileSync; - 写服务、要异步,优先用
fs.promises.readFile; - 大文件千万别用
readFileSync,改用流式读取; - 不论哪种写法,都要用 try/catch 或 err 判断兜住读取失败。
下一步建议把 Node.js 的事件循环和异步机制补上,理解了“为什么同步会阻塞”,你才算真正吃透文件读取。
延伸学习
想把这块知识系统补齐,可以按这个顺序来:
- 先过一遍 Node.js 入门课程,把模块系统和 fs 基础铺平;
- 语法记不住时,翻 Node.js 文档教程 查具体 API 参数;
- 想深究前后端关系,这篇 学前端要学 Node.js 吗 讲得更透,值得延伸阅读。
常见问题
Q:readFileSync 和 readFile 读出来的内容有区别吗?
A:没有本质区别,都是文件原文。区别只在“等待方式”:前者同步阻塞、直接给返回值,后者异步、结果放进回调。编码参数都传 'utf8' 时,返回的都是字符串,选哪个取决于你的代码能不能阻塞主线程。
Q:读取大文件为什么会内存溢出?
A:readFile 系列会一次性把整个文件读进内存再返回,文件超过可用内存就崩溃。改用 createReadStream 按块流动,内存只占一小块缓冲区,几百 MB 的文件也能稳稳处理。
Q:用 async/await 读取多个文件怎么提速?
A:把每个 fs.promises.readFile 调用当成独立 Promise,用 Promise.all([a, b, c]) 并行发起,整体耗时约等于最慢那一个,而不是三者相加。注意并发数太多要自己做限流,避免触及文件描述符上限。

TRAE-AI编程



