fs.promises是 Node.jsfs模块提供的Promise 化文件系统 API,替代了传统的回调式fs方法,天然适配async/await语法,是现代 Node.js 开发中处理文件系统的首选方式。
一、核心基础
1. 兼容性
- Node.js v10.0.0 起,
fs.promises被标记为稳定版; - Node.js v14.0.0 起,支持
import fs from 'fs/promises'(更简洁的导入方式)。
2. ESM 配置前提
使用 ESM 格式(import/export)需满足以下任一条件:
- 将文件后缀改为
.mjs; - 在项目根目录的
package.json中添加"type": "module"。
3. 导入方式
推荐使用更简洁的方式(Node.js 14+):
// 推荐:直接导入 promises 模块importfsfrom'fs/promises';// 配合路径处理(ESM 内置模块)importpathfrom'path';兼容更低版本(Node.js 10+):
import{promisesasfs}from'fs';importpathfrom'path';二、核心方法 & 代码示例
(一)文件基础操作
1. 读取文件(fs.readFile)
读取文件内容,支持文本/二进制格式,参数说明:
path:文件路径(推荐用path.resolve处理);options:可选,包含encoding(编码,如utf8)、flag(操作模式,如r只读)。
示例1:读取文本文件
asyncfunctionreadTextFile(){// 绝对路径(避免相对路径陷阱)constfilePath=path.resolve(__dirname,'example.txt');try{constcontent=awaitfs.readFile(filePath,{encoding:'utf8',// 文本编码,省略则返回 Bufferflag:'r'// 只读模式(默认)});console.log('文件内容:\n',content);}catch(err){console.error('读取失败:',err.message);}}readTextFile();示例2:读取二进制文件(图片/视频)
asyncfunctionreadBinaryFile(){constimgPath=path.resolve(__dirname,'image.png');try{// 不指定 encoding,返回 Bufferconstbuffer=awaitfs.readFile(imgPath);console.log('文件大小:',buffer.length,'字节');// 可直接写入新文件(复制二进制文件)awaitfs.writeFile(path.resolve(__dirname,'copy-image.png'),buffer);}catch(err){console.error('读取二进制文件失败:',err.message);}}readBinaryFile();2. 写入文件(fs.writeFile)
写入内容到文件,默认覆盖原有内容,参数说明:
path:目标文件路径;data:写入内容(字符串/Buffer/Uint8Array);options:可选,包含encoding、flag(w覆盖、a追加、wx原子写入(文件存在则失败))、mode(文件权限,如0o644)。
示例:写入/追加文件
asyncfunctionwriteFileExample(){constfilePath=path.resolve(__dirname,'output.txt');constcontent=`Hello fs.promises! 当前时间:${newDate().toISOString()}`;try{// 1. 覆盖写入(默认 flag: 'w')awaitfs.writeFile(filePath,content,{encoding:'utf8',mode:0o644// 权限:所有者读写,其他只读});console.log('覆盖写入成功');// 2. 追加内容(两种方式)awaitfs.appendFile(filePath,'\n这是追加的内容',{encoding:'utf8'});// 或用 writeFile + flag: 'a'// await fs.writeFile(filePath, '\n另一种追加方式', { flag: 'a' });}catch(err){console.error('写入失败:',err.message);}}writeFileExample();3. 复制文件(fs.copyFile)
复制文件,支持原子操作,参数:
src:源文件路径;dest:目标文件路径;mode:可选,如fs.constants.COPYFILE_EXCL(目标存在则报错)。
asyncfunctioncopyFileExample(){constsrc=path.resolve(__dirname,'source.txt');constdest=path.resolve(__dirname,'target.txt');try{// 基础复制(目标存在则覆盖)awaitfs.copyFile(src,dest);console.log('文件复制成功');// 原子复制(目标存在则报错)// await fs.copyFile(src, dest, fs.constants.COPYFILE_EXCL);}catch(err){console.error('复制失败:',err.message);}}copyFileExample();4. 重命名/移动文件(fs.rename)
既可以重命名文件,也可以移动文件(跨目录)。
asyncfunctionrenameFileExample(){constoldPath=path.resolve(__dirname,'old-name.txt');constnewPath=path.resolve(__dirname,'new-name.txt');// 移动到子目录constmovePath=path.resolve(__dirname,'subdir/new-name.txt');try{// 1. 重命名awaitfs.rename(oldPath,newPath);console.log('重命名成功');// 2. 移动(需确保子目录存在)awaitfs.mkdir(path.dirname(movePath),{recursive:true});awaitfs.rename(newPath,movePath);console.log('移动文件成功');}catch(err){console.error('重命名/移动失败:',err.message);}}renameFileExample();5. 删除文件(fs.unlink / fs.rm)
fs.unlink:传统删除文件方法;fs.rm(Node.js 14+):更通用,支持文件/目录,推荐使用。
asyncfunctiondeleteFileExample(){constfilePath=path.resolve(__dirname,'to-delete.txt');try{// 推荐:fs.rm(force: true 避免文件不存在时报错)awaitfs.rm(filePath,{force:true});console.log('文件删除成功');// 传统方式:fs.unlink// await fs.unlink(filePath);}catch(err){console.error('删除失败:',err.message);}}deleteFileExample();6. 获取文件信息(fs.stat / fs.lstat)
fs.stat:获取文件/目录信息;fs.lstat:区分符号链接(返回链接本身信息,而非目标文件)。
asyncfunctiongetFileStat(){constfilePath=path.resolve(__dirname,'example.txt');try{conststats=awaitfs.stat(filePath);console.log('文件信息:',{isFile:stats.isFile(),// 是否是文件isDirectory:stats.isDirectory(),// 是否是目录size:stats.size,// 大小(字节)birthtime:stats.birthtime,// 创建时间mtime:stats.mtime// 修改时间});}catch(err){console.error('获取文件信息失败:',err.message);}}getFileStat();(二)目录操作
1. 创建目录(fs.mkdir)
支持创建多级目录(recursive: true)。
asyncfunctioncreateDirExample(){// 多级目录constdirPath=path.resolve(__dirname,'new-dir/sub-dir/child-dir');try{awaitfs.mkdir(dirPath,{recursive:true,// 自动创建不存在的父目录mode:0o755// 目录权限:所有者读写执行,其他读执行});console.log('多级目录创建成功');}catch(err){console.error('创建目录失败:',err.message);}}createDirExample();2. 读取目录(fs.readdir)
读取目录下的文件/子目录,withFileTypes: true可获取文件类型信息。
asyncfunctionreadDirExample(){constdirPath=path.resolve(__dirname,'new-dir');try{// withFileTypes: true 返回 Dirent 对象(包含类型信息)constdirents=awaitfs.readdir(dirPath,{withFileTypes:true});dirents.forEach(dirent=>{consttype=dirent.isFile()?'文件':dirent.isDirectory()?'目录':'其他';console.log(`名称:${dirent.name},类型:${type}`);});}catch(err){console.error('读取目录失败:',err.message);}}readDirExample();3. 删除目录(fs.rmdir / fs.rm)
fs.rmdir:传统删除目录(需空目录,Node.js 12+ 支持recursive: true删除非空);fs.rm(Node.js 14+):推荐,支持递归删除非空目录。
asyncfunctiondeleteDirExample(){constdirPath=path.resolve(__dirname,'new-dir');try{// 推荐:fs.rm 递归删除(force: true 避免目录不存在时报错)awaitfs.rm(dirPath,{recursive:true,force:true});console.log('目录删除成功');// 传统方式:fs.rmdir(需 recursive: true)// await fs.rmdir(dirPath, { recursive: true });}catch(err){console.error('删除目录失败:',err.message);}}deleteDirExample();(三)其他常用方法
1. 检查文件可访问性(fs.access)
验证文件/目录的权限(读/写/执行)或是否存在。
asyncfunctioncheckAccessExample(){constfilePath=path.resolve(__dirname,'example.txt');try{// 检查是否存在(fs.constants.F_OK)awaitfs.access(filePath,fs.constants.F_OK);console.log('文件存在');// 检查是否可读(fs.constants.R_OK)awaitfs.access(filePath,fs.constants.R_OK);console.log('文件可读');// 检查是否可写(fs.constants.W_OK)awaitfs.access(filePath,fs.constants.W_OK);console.log('文件可写');}catch(err){console.error('访问检查失败:',err.message);}}checkAccessExample();2. 符号链接操作(fs.symlink / fs.readlink)
asyncfunctionsymlinkExample(){consttarget=path.resolve(__dirname,'example.txt');// 目标文件constlinkPath=path.resolve(__dirname,'example-link.txt');// 链接文件try{// 创建符号链接awaitfs.symlink(target,linkPath);console.log('符号链接创建成功');// 读取链接指向的路径constrealPath=awaitfs.readlink(linkPath);console.log('链接指向:',realPath);}catch(err){console.error('符号链接操作失败:',err.message);}}symlinkExample();三、错误处理
fs.promises所有方法返回的 Promise 失败时会抛出错误,需用try/catch捕获,常见错误码:
| 错误码 | 含义 |
|---|---|
| ENOENT | 文件/目录不存在 |
| EACCES | 权限不足 |
| EEXIST | 文件/目录已存在(原子操作时) |
| EPERM | 操作不被系统允许 |
| EISDIR | 操作对象是目录(但方法要求文件) |
示例:针对性错误处理
asyncfunctionerrorHandlingExample(){constfilePath=path.resolve(__dirname,'non-existent.txt');try{awaitfs.readFile(filePath,'utf8');}catch(err){switch(err.code){case'ENOENT':console.error('错误:文件不存在');break;case'EACCES':console.error('错误:无读取权限');break;default:console.error('未知错误:',err.message);}}}errorHandlingExample();四、关键注意事项
1. ESM 中 __dirname/__filename 替代
ESM 中没有__dirname/__filename,需手动实现:
import{fileURLToPath}from'url';import{dirname}from'path';const__filename=fileURLToPath(import.meta.url);// 当前文件路径const__dirname=dirname(__filename);// 当前文件目录2. 大文件处理
fs.readFile/fs.writeFile会将整个文件加载到内存,大文件(>100MB)推荐使用流:
import{createReadStream,createWriteStream}from'fs';import{pipeline}from'stream/promises';// 流的 Promise 化 APIasyncfunctionstreamLargeFile(){constsrc=path.resolve(__dirname,'large-file.txt');constdest=path.resolve(__dirname,'copy-large-file.txt');try{// 流式复制大文件awaitpipeline(createReadStream(src),createWriteStream(dest));console.log('大文件复制成功');}catch(err){console.error('大文件处理失败:',err.message);}}streamLargeFile();3. 批量操作优化
批量处理文件时,用Promise.all并行执行(注意系统文件描述符限制):
asyncfunctionbatchReadFiles(){constfiles=['file1.txt','file2.txt','file3.txt'].map(f=>path.resolve(__dirname,f));try{// 并行读取(失败时捕获单个错误,不阻塞整体)constresults=awaitPromise.all(files.map(file=>fs.readFile(file,'utf8').catch(err=>({file,error:err.message}))));results.forEach((res,idx)=>{if(res.error){console.error(`读取${files[idx]}失败:`,res.error);}else{console.log(`读取${files[idx]}成功:`,res.substring(0,50)+'...');}});}catch(err){console.error('批量操作异常:',err.message);}}batchReadFiles();4. 原子操作
使用flag参数实现原子写入(避免并发覆盖):
wx:写入文件,若文件已存在则失败;ax:追加内容,若文件不存在则失败。
asyncfunctionatomicWrite(){constfilePath=path.resolve(__dirname,'atomic.txt');try{awaitfs.writeFile(filePath,'原子写入内容',{flag:'wx'});console.log('原子写入成功');}catch(err){if(err.code==='EEXIST'){console.error('文件已存在,原子写入失败(避免覆盖)');}}}atomicWrite();五、总结
fs.promises是 Node.js 处理文件系统的现代方案,核心优势:
- 摆脱回调地狱,代码更易读、易维护;
- 适配
async/await,错误处理更清晰; - API 语义化,与传统
fs方法一一对应,学习成本低。