Node.js FS Promises:現代檔案操作的最佳實踐

自 Node.js 10 版本引入 fs/promises 以來,處理檔案系統的方式發生了革命性的變化。這套 API 讓我們能夠直接運用 PromiseAsync/Await,徹底告別傳統的回呼地獄 (Callback Hell)。

為什麼應該全面轉向 Promises API?

  1. 結構扁平化:多步驟的檔案操作可以線性撰寫,邏輯極度清晰。
  2. 原生支援:不再需要透過 util.promisify 進行轉換,減少額外開銷。
  3. 優雅的錯誤處理:直接使用標準的 try...catch 捕捉所有異常。

基礎實戰:讀取與更新設定檔

這是開發中常見的流程:讀取 JSON、修改內容、寫回檔案。

const fs = require('fs/promises');

async function updateConfig() {
  try {
    // 1. 讀取並解析
    const content = await fs.readFile('./config.json', 'utf8');
    const config = JSON.parse(content);

    // 2. 修改邏輯
    config.lastLogin = new Date().toLocaleString();
    config.visits += 1;

    // 3. 寫回檔案 (保持排版整齊)
    await fs.writeFile('./config.json', JSON.stringify(config, null, 2));

    console.log('配置更新成功!');
  } catch (err) {
    console.error('檔案操作失敗:', err.message);
  }
}

進階技巧與現代 API

1. 遞迴建立目錄

不再需要判斷資料夾是否存在,直接使用 recursive 選項即可一鍵建立整條路徑。

await fs.mkdir('./logs/2025/january', { recursive: true });

2. 檔案存取檢查 (取代 fs.exists)

使用 access 來判斷檔案是否存在或是否具有特定權限,而不是使用已廢棄的 exists

async function isExisted(path) {
  try {
    await fs.access(path);
    return true;
  } catch (err) {
    return false;
  }
}

3. 使用 FileHandle 進行精細控制

對於需要多次讀寫的同個檔案,使用 open() 獲取 FileHandle 效能更好且更可控。

const handle = await fs.open('temp.txt', 'r+');
try {
  const { buffer } = await handle.read(Buffer.alloc(10), 0, 10, 0);
  console.log('讀到前 10 字節:', buffer.toString());
} finally {
  await handle.close(); // 務必記得關閉 handle 以釋放描述符
}

常見實務:掃描目錄檔案

利用 readdir 配合 withFileTypes 選項,可以輕鬆分辨出目錄下哪些是檔案、哪些是資料夾。

async function scanDir(path) {
  const entries = await fs.readdir(path, { withFileTypes: true });
  for (const entry of entries) {
    const type = entry.isDirectory() ? '[Dir]' : '[File]';
    console.log(`${type} - ${entry.name}`);
  }
}

總結

  1. fs/promises 是現代 Node.js 應用的標配。
  2. 對於複雜的非同步流程,它能顯著降低開發維護成本。
  3. 雖然 Promise 很強大,但面對 GB 等級 的超大檔案,請回歸 Stream 串流模式 才是最佳方案。