Node.js fs 文件系统模块 四种读写方案全解析

fs 是 Node 内置文件系统模块,用于读写文件、操作文件夹。JS 是单线程语言,fs 提供同步阻塞、回调异步、Promise、async/await 四种写法,一步步解决异步嵌套带来的回调地狱问题。

一、模块导入两种方式

  1. 基础 fs:提供同步 API、回调风格异步 API

js

运行

javascript 复制代码
import fs from 'fs';
  1. fs/promises:专门提供 Promise 风格 API,适配 async/await(推荐生产使用)

js

运行

javascript 复制代码
import fs from 'fs/promises';

二、方案 1:同步读取 readFileSync(阻塞主线程)

特点:代码顺序执行,文件读取期间阻塞整个线程,不适合接口、循环大量 IO;仅适合项目启动一次性读取配置文件。

js

运行

ini 复制代码
import fs from 'fs';
const syncData = fs.readFileSync('./test.txt', 'utf-8');
console.log(syncData);

缺点:并发场景大量文件读写会严重降低服务性能。

三、方案 2:回调异步 readFile(ES6 原生写法,回调地狱)

Node 统一错误优先回调规范:(err, data) => {}

  • 读取成功:err = null,data 为文件内容
  • 读取失败:err 为错误对象(文件不存在、权限不足等)

串行读取多个文件时会产生多层嵌套,即回调地狱,维护成本极高。

js

运行

javascript 复制代码
import fs from 'fs';
fs.readFile('./file1.txt', 'utf-8', (err, data) => {
  if (!err) {
    console.log('file1', data);
  } else {
    console.log(err);
  }
  fs.readFile('./file2.txt', 'utf-8', (err, data) => {
    if (!err) {
      console.log('file2', data);
    } else {
      console.log(err);
    }
    fs.readFile('./file3.txt', 'utf-8', (err, data) => {
      if (!err) {
        console.log('file3', data);
      } else {
        console.log(err);
      }
    })
  })
})

四、方案 3:Promise 链式调用(消除嵌套)

基于 fs/promises,readFile 返回 Promise,使用 .then() 链式串行执行,解决多层嵌套问题;缺点是多文件场景重复书写 then,代码冗余。

js

运行

kotlin 复制代码
import fs from 'fs/promises';
fs.readFile('./file1.txt', 'utf-8')
  .then(data => {
    console.log('file1', data);
    return fs.readFile('./file2.txt', 'utf-8');
  })
  .then(data => {
    console.log('file2', data);
    return fs.readFile('./file3.txt', 'utf-8');
  })
  .then(data => {
    console.log('file3', data);
  })

五、方案 4:async/await(ES8 最优方案,生产首选)

async/await 是 Promise 的语法糖,底层依旧异步、不会阻塞主线程;代码线性自上而下书写,可读性最强。限制:await 只能写在 async 函数内,顶层直接 await 会报错,使用 IIFE 立即执行函数包裹。补充:Promise、await 属于微任务;setTimeout 属于宏任务。

js

运行

javascript 复制代码
import fs from 'fs/promises';
(async () => {
  const file1Data = await fs.readFile('./file1.txt', 'utf-8');
  console.log('file1', file1Data);
  const file2Data = await fs.readFile('./file2.txt', 'utf-8');
  console.log('file2', file2Data);
  const file3Data = await fs.readFile('./file3.txt', 'utf-8');
  console.log('file3', file3Data);
})();

完整健壮写法(增加 try/catch 捕获读取错误)

js

运行

javascript 复制代码
import fs from 'fs/promises';
(async () => {
  try {
    const data = await fs.readFile('./test.txt', 'utf-8');
    console.log(data);
  } catch (err) {
    console.error('文件读取失败:', err);
  }
})();

六、异步代码演进路线

同步阻塞(性能差)→ 回调函数(回调地狱)→ Promise then 链式(冗余)→ async/await(简洁易维护)

七、四种读取方式对比表

表格

写法 是否阻塞线程 优点 缺点
readFileSync 阻塞 写法极简 大量 IO 严重影响服务性能
readFile 回调 非阻塞 无需额外导入 串行多文件产生回调地狱
Promise then 非阻塞 无嵌套结构 多文件链式代码重复繁琐
async await 非阻塞 线性阅读,代码整洁 ES8 语法,需 async 函数包裹

八、工程开发最佳实践

  1. 业务文件读写统一使用 fs/promises + async/await
  2. 所有文件读取必须搭配 try/catch 捕获异常;
  3. 同步 readFileSync 仅用于项目初始化加载配置,接口业务禁止使用;
  4. 路径搭配 path.resolve 生成绝对路径,防止运行目录变更导致文件找不到。
相关推荐
Larcher1 小时前
从回调地狱到优雅异步:Node.js 文件操作进化史
javascript·后端·架构
Java编程爱好者1 小时前
深入浅出Agent: Harness最全调研与原理解析(持续更新中)
后端
日月云棠1 小时前
Spring 源码深度剖析:DispatcherServlet 的自动化注册机制
java·后端
熊猫钓鱼>_>1 小时前
Electron:当 Web 技术统治桌面
大数据·前端·javascript·人工智能·架构·electron·agent
码事漫谈2 小时前
让 Codex CLI 跑上 DeepSeek:用 LiteLLM 做 Responses↔Chat 协议翻译
后端
JavaGuide2 小时前
又一个画图 Skill 开源,再见手动画 draw.io!
前端·后端·github
吾诺2 小时前
2026大直径紧固件双叠自锁垫圈怎么选型
后端
tyung2 小时前
zhenyi-base zqueue 基准测试分享
后端·go
江华森2 小时前
Python 实现高德地图找房(三):地图可视化与高德 JS API
开发语言·javascript·python