如何把 Node.js 的回调函数 API 转换为 Promise?
把回调转 Promise:手动用 new Promise 包装(error-first 回调调 reject/resolve),或用 Node 内置 util.promisify 一行转换。本文给出两种方法、参数处理和常见坑。
两种方法:手动 new Promise 包装,或直接用内置 util.promisify() 一键转换。
方法一:util.promisify(推荐)
Node 回调遵循 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 化之后还能用回调调用吗?
Promise 版函数不再接受回调参数。要同时支持两种风格,得在函数里检测最后一个参数是否为函数,不建议在新代码这么做。
Q:fs 模块还要手动 promisify 吗?
不用。require('fs').promises 提供全套 Promise 版本,直接用。
Q:转成 Promise 后错误处理怎么写?
用 async/await 就配 try/catch;用链式就 .catch()。注意别混用回调和 Promise 两种错误处理,容易漏捕获。