Node.js util.promisify:將 Callback 舊函式轉為 Promise
雖然現代 Node.js 開發全面擁抱 Async/Await,但在龐大的 npm 生態系中,仍有許多優秀的舊模組採用 Error-First Callback 的風格。為了讓程式碼風格統一,我們需要將這些舊 API 轉換為 Promise 形式。
使用內建的 util.promisify
Node.js util 模組提供了一個神奇的函數 promisify。只要目標函數符合「最後一個參數是 Callback」且「Callback 第一個參數是 Error」的慣例,它就能自動完成轉換。
範例:轉換檔案讀取工具
const fs = require('fs');
const { promisify } = require('util');
// 將舊式的 fs.readFile 轉換為 Promise 版本
const readFileAsync = promisify(fs.readFile);
async function getConfig() {
try {
const data = await readFileAsync('settings.json', 'utf8');
console.log('配置內容:', data);
} catch (err) {
console.error('讀取失敗:', err.message);
}
}
getConfig();
手動封裝 (Manual Promisification)
並非所有函式都那麼聽話。如果你遇到不符合標準規範(例如 Callback 不是最後一個參數,或沒有錯誤優先設計)的函式,你就需要手動用 new Promise 進行包裝:
// 假設這是一個舊套件的函式,且 Callback 設計不標準
function legacyTask(id, callback, options) {
setTimeout(() => {
callback(`任務 ${id} 完成`);
}, 1000);
}
// 手動轉換為 Promise 版本
function taskWrapper(id, options) {
return new Promise((resolve, reject) => {
try {
legacyTask(
id,
(result) => {
resolve(result);
},
options
);
} catch (err) {
reject(err);
}
});
}
現代 Node.js 的「去 Promisify」趨勢
值得注意的是,Node.js 官方意識到這層轉換的繁瑣,因此在常用的核心模組中,現在都直接封裝好了 Promise 版本的入口,通常位於 /promises 下。
這代表你不再需要手動呼叫 promisify:
// 核心模組推薦寫法
const fs = require('fs/promises'); // 推薦
const dns = require('dns/promises');
const { setTimeout } = require('timers/promises');
async function modernApp() {
await setTimeout(1000); // 真正非阻塞的延遲
const data = await fs.readFile('info.txt', 'utf8');
}
反向操作:如果你極有必要將 Promise 轉回 Callback 風格,可以使用
util.callbackify,這在某些需要與舊框架橋接的場景中會用到。總結
util.promisify是銜接新舊時代的橋樑。- 它只對符合 Error-First 慣例的函數有效。
- 優先尋找內建的
/promises模組,以保持代碼簡潔。