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 生成绝对路径,防止运行目录变更导致文件找不到。
相关推荐
拔剑纵狂歌14 分钟前
ClickHouse system.query_log 字段详解与排障实践
后端·clickhouse·1024程序员节
再吃一根胡萝卜24 分钟前
把本地 RAG 问答做"准":多轮指代消解 + 云端语义向量 + TXT 章节切分
后端
怕浪猫1 小时前
2026年金九银十,我面了22个前端
前端·javascript·面试
怕浪猫1 小时前
2026年9月面18个后端
后端·面试·github
IT_陈寒1 小时前
Vue 这个响应式陷阱,我的头发都掉没了
前端·人工智能·后端
谁在黄金彼岸1 小时前
trae的gitee mcp莫名错误
后端
前端的日常1 小时前
一个人+AI做情侣食谱小程序,30天纯赚
前端·javascript·后端
再吃一根胡萝卜1 小时前
给 RAG 问答加上「多轮指代消解」:让"他 / 这个"不再翻车
后端
qq_316837752 小时前
阿里云百炼 fun-asr-realtime uniapp vue3 调用
javascript·vue.js·uni-app
默_笙2 小时前
🚊 别再让 AI 的记忆写两遍了:PostgreSQL 把关系查询和语义检索装进一张表
前端·javascript