如何把 Node.js 的回调函数 API 转换为 Promise?
把回调转 Promise:手动用 new Promise 包装(error-first 回调调 reject/resolve),或用 Node 内置 util.promisify 一行转换。本文给出两种方法、参数处理和常见坑。
两种方法:手动 new Promise 包装,或直接用内置 util.promisify() 一键转换。
方法一:util.promisify(推荐)
最后一个参数为 error-first 回调 callback(err, result) 的函数,一行搞定:
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
readFile('a.txt', 'utf8').then(console.log).catch(console.error);
方法二:手动包装(理解原理)
// 原始回调式函数
function fetchData(callback) {
setTimeout(() => {
callback(null, { result: '一些数据' });
}, 1000);
}
// 包装成 Promise
function fetchDataPromise() {
return new Promise((resolve, reject) => {
fetchData((error, data) => {
if (error) reject(error); // error-first:有错 reject
else resolve(data); // 成功 resolve
});
});
}
// 现在可以 await 了
fetchDataPromise().then(console.log).catch(console.error);
要点:回调的第一个参数是错误——有 err 走 reject,没 err 走 resolve,顺序别反。
特殊情况处理
回调有多个成功值(如 callback(null, user, meta)):
// promisify 默认只取第一个成功值;自定义取全部
fn[util.promisify.custom] = (a) => new Promise((res, rej) => {
fn(a, (err, user, meta) => err ? rej(err) : res({ user, meta }));
});
const custom = util.promisify(fn); // 先定义自定义实现,再转换
手动包装则直接在 resolve 里组装:resolve({ user, meta })。
this 依赖:有些函数依赖调用时的 this(如对象方法),用 promisify 时 .call(obj, ...) 或先 bind:util.promisify(obj.method).bind(obj)。
常见问题(FAQ)
Q:Promise 化之后还能用回调调用吗?
promisify 返回新的 Promise 包装函数,原函数仍可按原回调方式调用;不是把原 API 改写掉。
Q:fs 模块还要手动 promisify 吗?
不用。常见文件操作优先用 fs/promises;流和监听等 API 并非都能一一转成 Promise。
Q:转成 Promise 后错误处理怎么写?
用 async/await 就配 try/catch;用链式就 .catch()。同步抛错会被 Promise executor 捕获,但后来异步回调中的独立抛错不会自动变成拒绝。回调多次调用时只会采用第一次结算,事件流不能当作一次性 Promise。已返回 Promise 的函数应直接使用,当前 Node 已弃用对这类函数再 promisify。