如何把 Node.js 的回调函数 API 转换为 Promise?

把回调转 Promise:手动用 new Promise 包装(error-first 回调调 reject/resolve),或用 Node 内置 util.promisify 一行转换。本文给出两种方法、参数处理和常见坑。

最佳实践
Node.js 事件循环与异步任务示意

两种方法:手动 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。

参考资料

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台