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 生成绝对路径,防止运行目录变更导致文件找不到。
相关推荐
vx-Biye_Design1 小时前
SSM伴侣动物伴护星小程序06330-计算机课程设计、毕业设计
spring boot·后端·elasticsearch·小程序·架构·课程设计·idea
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
面向Google编程3 小时前
Apache Iceberg Variant 类型:v3 半结构化数据不再「二选一」
大数据·后端
passerby60613 小时前
如何自己造一个时间处理库
前端·javascript·github
jason成都3 小时前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
小羊没烦恼!4 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
BillKu8 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss