三套 API

fs 模块有三种风格:

import fs from 'fs';                       // 回调 API
import fsSync from 'fs';                    // 同步(同上面,加 Sync 后缀)
import fsp from 'fs/promises';              // Promise API ★ 推荐

Promise API(推荐)

import { readFile, writeFile, readdir, stat, mkdir, rm } from 'fs/promises';

// 读
const content = await readFile('config.json', 'utf8');
const json = JSON.parse(content);

// 写
await writeFile('output.txt', 'Hello');
await writeFile('output.txt', 'Hello', 'utf8');
await writeFile('binary.bin', Buffer.from([1, 2, 3]));

// 追加
import { appendFile } from 'fs/promises';
await appendFile('log.txt', 'new line\n');

// 列目录
const files = await readdir('./src');
const filesWithType = await readdir('./src', { withFileTypes: true });

// 看文件信息
const info = await stat('config.json');
console.log(info.size, info.isFile(), info.isDirectory());

// 建目录
await mkdir('./output', { recursive: true });   // 自动建中间层

// 删
await rm('./tmp', { recursive: true, force: true });    // -rf 风格
await rm('./file.txt');                                  // 删文件

同步 API(启动期 / 配置加载用)

import { readFileSync, writeFileSync, existsSync } from 'fs';

const config = JSON.parse(readFileSync('config.json', 'utf8'));
writeFileSync('output.txt', 'data');

if (existsSync('./tmp')) {
    // ...
}

只在启动期 / CLI 工具用同步——服务运行期同步 = 阻塞事件循环。

检查文件是否存在

// ❌ 老式(有竞态条件)
if (existsSync('./file.txt')) {
    await readFile('./file.txt');    // 这中间文件可能没了
}

// ✓ 直接尝试 + catch
try {
    const data = await readFile('./file.txt', 'utf8');
} catch (err) {
    if (err.code === 'ENOENT') {
        // 不存在
    } else {
        throw err;
    }
}

流式读写大文件

小文件用 readFile大文件(几百 MB+)用流

import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';

await pipeline(
    createReadStream('big.txt'),
    createWriteStream('big-copy.txt'),
);

pipeline 自动处理错误 + 关流。详见 19-streams。

边读边处理(line-by-line)

import { createReadStream } from 'fs';
import { createInterface } from 'readline';

const rl = createInterface({
    input: createReadStream('log.txt'),
    crlfDelay: Infinity,
});

for await (const line of rl) {
    if (line.includes('ERROR')) console.log(line);
}

观察文件变化

import { watch } from 'fs/promises';

(async () => {
    const watcher = watch('./src', { recursive: true });
    for await (const event of watcher) {
        console.log(event.eventType, event.filename);
    }
})();

实用例子

递归找文件

import { readdir } from 'fs/promises';
import { join } from 'path';

async function find(dir, pattern) {
    const entries = await readdir(dir, { withFileTypes: true });
    const results = [];
    for (const e of entries) {
        const full = join(dir, e.name);
        if (e.isDirectory()) {
            results.push(...await find(full, pattern));
        } else if (pattern.test(e.name)) {
            results.push(full);
        }
    }
    return results;
}

const tsFiles = await find('./src', /\.ts$/);

或用 fs.glob(Node 22+)/ fast-glob 库。

复制目录

import { cp } from 'fs/promises';
await cp('./src', './dest', { recursive: true });

临时文件

import { mkdtemp } from 'fs/promises';
import { tmpdir } from 'os';

const tmp = await mkdtemp(join(tmpdir(), 'myapp-'));
// 用完 rm -rf
await rm(tmp, { recursive: true, force: true });

常用 stat 字段

const s = await stat('file.txt');
s.size                              // 字节
s.isFile()
s.isDirectory()
s.isSymbolicLink()
s.mtime                             // 最后修改
s.ctime                             // 元数据改动
s.atime                             // 访问时间

  • 同步 API 阻塞循环——服务运行期别用
  • readFile 不带 encoding 返回 Buffer——文本要 'utf8'
  • 路径分隔符跨平台用 path.join,别拼字符串
  • rmforce: true 才不会因不存在报错(等于 rm -f

下一篇:path / os / process。