Node.js 的内置 util 包有一个 promisify() 函数 转换 回调 基于 Promise 函数的函数。 这使您可以将 Promise 链 和 async/await 与基于回调的 API 一起使用。
例如,Node.js fs 使用回调。 通常要读取文件,您需要使用回调:
const fs = require('fs');
fs.readFile('./package.json', function callback(err, buf) {
const obj = JSON.parse(buf.toString('utf8'));
obj.name; // 'masteringjs.io'
});
您可以使用 util.promisify() 转换 fs.readFile() 函数到返回回调的函数:
const fs = require('fs');
const util = require('util');
// Convert `fs.readFile()` into a function that takes the
// same parameters but returns a promise.
const readFile = util.promisify(fs.readFile);
// You can now use `readFile()` with `await`!
const buf = await readFile('./package.json');
const obj = JSON.parse(buf.toString('utf8'));
obj.name; // 'masteringjs.io'
如何 util.promisify() 在引擎盖下工作? 有一个 polyfill ,你可以在 这里阅读完整的实现 。您也可以在 此处找到 Node.js 实现 ,尽管出于教育目的,polyfill 更易于阅读。
背后的关键思想 util.promisify() 就是它 给你传入的参数增加了一个回调函数 。该回调函数解析或拒绝承诺函数返回的承诺。这有点拗口,所以这是一个非常简化的自定义实现示例 util.promisify()。
const fs = require('fs');
// A simplified implementation of `util.promisify()`. Doesn't
// cover all cases, don't use this in prod!
function promisify(fn) {
return function() {
const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
fn.apply(this, [].concat(args).concat([(err, res) => {
if (err != null) {
return reject(err);
}
resolve(res);
}]));
});
};
}
// Convert `fs.readFile()` into a function that takes the
// same parameters but returns a promise.
const readFile = promisify(fs.readFile);
// You can now use `readFile()` with `await`!
const buf = await readFile('./package.json');
const obj = JSON.parse(buf.toString('utf8'));
obj.name; // 'masteringjs.io'
那么这是什么意思?
第一 util.promisify() 向您传入的参数添加 1 个额外参数,然后使用这些新参数调用原始函数。 这意味着底层函数需要支持该数量的参数。 所以如果你正在调用一个 Promise 函数 myFn() 有 2 个类型的参数 [String, Object],确保原始函数支持调用签名 [String, Object, Function]。
第二 util.promisify() 对 函数上下文 。
丢失上下文 意味着函数调用以错误的值结束 this, 丢失上下文是转换函数的常见问题:
class MyClass {
myCallbackFn(cb) {
cb(null, this);
}
}
const obj = new MyClass();
const promisified = require('util').promisify(obj.myCallbackFn);
const context = await promisified();
context; // `undefined` instead of a `MyClass` instance!
请记住 this 包含该函数在调用时的属性的任何对象。 因此您可以通过将 promisified 函数设置为同一对象的属性来保留上下文:
class MyClass {
myCallbackFn(cb) {
cb(null, this);
}
}
const obj = new MyClass();
// Retain context because `promisified` is a property of `obj`
obj.promisified = require('util').promisify(obj.myCallbackFn);
const context = await obj.promisified();
context === obj; // true
