☰
fs-extra `ensureFileSync` 完全指南:一行代码确保文件存在并自动补齐缺失目录
2026/9/25 2:02:00 网站建设 项目流程
  • 开发工具

【免费下载链接】node-fs-extra

Node.js: extra methods for the fs object like copy(), remove(), mkdirs()

项目地址:https://gitcode.com/gh_mirrors/no/node-fs-extra
点击查看免费下载

fs-extra为 Node.js 原生fs模块提供了大量便捷方法,其中ensureFileSync()用于确保目标文件一定存在:文件不存在则创建(其所在目录若也不存在则一并递归创建),文件已存在则原样保留、绝不改动内容。本文以 docs/ensureFile-sync.md 为骨架,结合 lib/ensure/file.js 源码与 lib/ensure/tests/ensure.test.js、lib/ensure/tests/create.test.js 测试用例,完整讲解该 API 的签名、底层原理、别名机制与实战用法,读完即可在项目里安全、无脑地初始化文件。

一、API 签名与核心语义

ensureFileSync是同步方法,签名与参数如下(直接继承自官方文档):

  • ensureFileSync(file)
  • file<String>:目标文件的绝对路径或相对路径

别名:createFileSync()——两个名字指向同一个实现,可任意混用。

核心行为(三条铁律):

  1. 文件不存在→ 创建它;
  2. 文件所在的目录不存在→ 递归创建这些目录(与mkdirsSync行为一致);
  3. 文件已存在→完全不修改(不截断、不覆盖、不动内容,也不更新时间戳)。

官方示例:

const fs = require('fs-extra') const file = '/tmp/this/path/does/not/exist/file.txt' fs.ensureFileSync(file) // file has now been created, including the directory it is to be placed in

执行后,/tmp/this/path/does/not/exist/这一整条目录链会被递归创建,file.txt以空文件形式诞生。

如果需要在异步场景中使用,请参见配套文档 docs/ensureFile.md,其同样支持回调(Callback)、Promise 与 async/await 三种写法。

二、源码级实现:ensureFileSync到底做了什么

核心实现在 lib/ensure/file.js,逻辑非常精巧,可用如下流程概括:

function createFileSync (file) { // 1. 文件已存在且是普通文件 → 直接返回,不碰内容 let stats try { stats = fs.statSync(file) } catch { } if (stats && stats.isFile()) return const dir = path.dirname(file) // 2. 检查父目录 try { if (!fs.statSync(dir).isDirectory()) { // 父路径是文件而非目录 → 故意触发 ENOTDIR 错误 fs.readdirSync(dir) } } catch (err) { // 3. 父目录不存在(ENOENT)→ 递归创建整条目录链 if (err && err.code === 'ENOENT') mkdir.mkdirsSync(dir) else throw err } // 4. 创建空文件 fs.writeFileSync(file, '') }

关键设计一:已存在即短路返回

第一步先用fs.statSync(file)探测目标,一旦stats && stats.isFile()成立便立即return。这意味着:

  • 对已存在的文件调用是零副作用的,不会改写内容、不会触发EACCES写入错误;
  • 对软链接指向的普通文件,statSync会跟随链接,因此同样视为“已存在”而跳过。

关键设计二:父路径是文件时如何抛错

如果file的父目录其实是一个文件(例如路径为/tmp/a.txt/b.txt,而/tmp/a.txt是文件),statSync(dir).isDirectory()返回false,此时代码刻意调用fs.readdirSync(dir)——注释写明“This is just to cause an internal ENOTDIR error to be thrown”,即借readdirSync对“非目录路径”报ENOTDIR的底层行为,向调用者抛出一个清晰、可捕获的错误码,而不是糊里糊涂地失败。

关键设计三:父目录缺失时递归补齐

当statSync(dir)抛出ENOENT时,调用mkdir.mkdirsSync(dir)(即mkdirsSync)先创建整条目录链,随后writeFileSync(file, '')写入空文件。两步缺一不可:只建目录不写文件、或只写文件不建目录,都无法满足“ensure”语义。

三、目录递归创建的底层依赖:mkdirs家族

ensureFileSync的目录能力完全复用mkdirs模块。在 lib/ensure/file.js 中它通过const mkdir = require('../mkdirs')引入,而 lib/mkdirs/index.js 将mkdirsSync指向make-dir.js中的makeDirSync:

module.exports.makeDirSync = (dir, options) => { checkPath(dir) return fs.mkdirSync(dir, { mode: getMode(options), // 默认 0o777 recursive: true // 递归创建多级目录 }) }
  • recursive: true是递归创建的关键,等价于mkdir -p;
  • 默认目录权限为0o777(最终受进程umask约束),可通过options.mode覆盖。

因此ensureFileSync('/a/b/c.txt')等价于手写fs.mkdirSync('/a/b', { recursive: true })+fs.writeFileSync('/a/b/c.txt', ''),但更简洁、更健壮。

四、别名机制:ensureFileSync与createFileSync是同一函数

在 lib/ensure/index.js 中,ensure 系列与 create 系列被显式绑定为同一引用:

const { createFile, createFileSync } = require('./file') // ... createFileSync, ensureFile: createFile, ensureFileSync: createFileSync,

也就是说:

  • fs.ensureFileSync === fs.createFileSync(同一函数对象);
  • fs.ensureFile === fs.createFile(异步版本同理)。

选择哪个名字纯粹是语义偏好:ensure(确保)强调幂等保证,create(创建)强调动作,功能完全等价。该文件同时导出了ensureLinkSync/ensureSymlinkSync等兄弟方法,构成完整的 ensure 家族。

五、行为边界:测试用例验证的三种场景

仓库测试从三个维度锁定了该 API 的契约(lib/ensure/tests/ensure.test.js):

场景预期行为测试断言
文件不存在,且目录链缺失递归创建目录并生成空文件assert(fs.existsSync(file))
文件已存在(内容为'blah')什么都不做,内容原样保留assert(fs.existsSync(file))
目标路径本身是一个目录抛错,错误码为EISDIRassert.strictEqual(e.code, 'EISDIR')

补充测试(lib/ensure/tests/create.test.js)还验证了:

  • 内容不被修改:先写入'hello world',再调用createFileSync,读取结果仍是'hello world';
  • 目录树中某个节点是文件:路径形如existingFile/xxx.txt时抛出ENOTDIR。

这些测试共同回答了一个关键问题:传入目录路径会得到EISDIR错误而非静默成功——ensureFileSync只保证“文件”的语义,不负责“目录”的语义(那属于 ensureDirSync)。

六、实战场景与组合用法

ensureFileSync最常见的价值是消灭“先建目录再写文件”的样板代码。以下场景均可直接套用:

场景 1:日志文件初始化

const fs = require('fs-extra') const logPath = 'logs/2026/09/app.log' fs.ensureFileSync(logPath) // logs/2026/09/ 自动创建 fs.appendFileSync(logPath, 'boot ok\n')

场景 2:配置文件占位

const fs = require('fs-extra') const path = require('path') const configPath = path.join(process.cwd(), 'config', 'local.json') fs.ensureFileSync(configPath) // 幂等:多次启动不会破坏已有配置 const content = fs.readFileSync(configPath, 'utf8') || '{}'

场景 3:与异步写文件配合

const fs = require('fs-extra') fs.ensureFileSync('/tmp/data/cache.json') // 同步补齐目录 fs.writeJson('/tmp/data/cache.json', { ok: true }) // 再异步写入内容

七、注意事项

  1. 同步阻塞:ensureFileSync会阻塞事件循环,仅适合启动初始化、配置准备等低频路径;高频场景应使用 ensureFile(Promise 版)。
  2. 创建的是空文件:它只负责“文件存在”,不写入任何内容,默认内容为空字符串。
  3. 目录权限:递归创建的目录默认模式为0o777(受umask影响),如需自定义可在底层mkdirsSync传入{ mode }选项。
  4. 路径校验:底层checkPath(见 lib/mkdirs/utils.js)会校验路径有效性;传入''、null等非法路径会直接抛错。
  5. 错误码语义:目标为目录 →EISDIR;路径中某节点是文件 →ENOTDIR;无写权限 →EACCES。捕获后按错误码分流即可。

八、小结

ensureFileSync(file)是一个“幂等初始化”利器:不存在就递归创建目录并生成空文件,已存在则零改动返回。其实现由“先statSync短路”“借readdirSync抛ENOTDIR”“复用mkdirsSync递归建目录”“writeFileSync写空文件”四步构成,配合 lib/ensure/tests/ensure.test.js 的契约测试,行为边界非常清晰。在日志、缓存、配置等文件的启动初始化中,它能把三五行 try/catch 压缩成一行调用,且比手写fs.existsSync + fs.mkdirSync + fs.writeFileSync的组合更可靠。

  • 开发工具

【免费下载链接】node-fs-extra

Node.js: extra methods for the fs object like copy(), remove(), mkdirs()

项目地址:https://gitcode.com/gh_mirrors/no/node-fs-extra
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询