util模块最初的目的是为内部API提供一些工具支持,然而很多工具函数对于普通的开发者来说也十分有用,因此util模块将一些方法实现了对外暴露。本文主要探讨以下三方面的工具函数:
- 风格转换
- 调试输出
- 废弃API
风格转换
callback转换promise
针对传入error-first回调作为函数的最后一个参数的函数(比如fs.readFile(‘./filename’, (err, data) => {})), util提供了promisify(original)方法用来将这种类型的函数转换成返回promise的形式。
以fs.readFile为例
const fs = require('fs');
fs.readFile('./h.js', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data.toString());
})
// 使用util.promisify转换后
const fs = require('fs');
const util = require('util');
const readFilePromise = util.promisify(fs.readFile);
readFilePromise('./h.js')
.then((data) => {
console.log(data.toString());
}, (err) => {
console.error(err);
});
具体实现
promisify执行完后返回的是一个新的函数,新的函数的执行结果是一个promise,新函数内部会调用original原有的方法并且会自动追加error-first类型的callback,根据original的执行结果判断是resolve还是reject,简易版本的代码如下:
function promisify(original) {
function fn(...args) {
const promise = createPromise();
try {
original.call(this, ...args, (err, ...values) => {
if (err) {
promiseReject(promise, err);
} else {
promiseResolve(promise, values[0]);
}
});
} catch (err) {
promiseReject(promise, err);
}
return promise;
}
return fn
}
util模块还提供了promisify的自定义转换方式(original函数上定义util.promisify.custom属性),比如通过下面的方式可以实现禁用文件读取,util.promisify.custom必须定义在util.promisify调用之前
const fs = require('fs');
const util = require('util');
fs.readFile[util.promisify.custom] = (fileName) => {
return Promise.reject('not allowed');
}
const readFilePromise = util.promisify(fs.readFile);
readFilePromise('./h.js')
.then((data) => {
console.log(data.toString());
}, (err) => {
console.error(err); // not allowed
});
promise转callback
util的callbackify方法与promisify刚好相反,callbackify用于把async(或者返回promise)的函数转换成遵从error-first回调风格的类型
const util = require('util');
const fn = () => {
return 'fn executed'
};
function delay(second, fn) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(fn());
}, second);
});
}
delay(1000, fn)
.then((data) => {
console.log(data); // fn executed
});
// 使用util.callbackify转换后
const delayFn = util.callbackify(delay);
delayFn(1000, fn, (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data); // fn executed
});
有一种情况需要关注,假如promise是reject(null || 0 || false)的话,那么callback的err判断为非,程序会继续执行console.log(data),这其实是不正确的。因此callbackify对于这种情况做了特殊的处理(创建一个error,将原始的信息放在error的reason属性上,把error传递给最终的回调函数)
function callbackifyOnRejected(reason, cb) {
if (!reason) {
const newReason = new ERR_FALSY_VALUE_REJECTION();
newReason.reason = reason;
reason = newReason;
Error.captureStackTrace(reason, callbackifyOnRejected);
}
return cb(reason);
}
具体实现
实现的逻辑是调用原始函数original通过then来调用callback方法
function callbackify(original) {
function callbackified(...args) {
const maybeCb = args.pop();
const cb = (...args) => { Reflect.apply(maybeCb, this, args); };
Reflect.apply(original, this, args)
.then((ret) => process.nextTick(cb, null, ret),
(rej) => process.nextTick(callbackifyOnRejected, rej, cb));
}
return callbackified;
}
调试输出
util.debuglog(section)
debuglog方法用于根据NODE_DEBUG环境变量来选择性的输出debug信息,例如下面的例子
//index.js
const util = require('util');
const debuglog = util.debuglog('foo-bar');
debuglog('hello from foo [%d]', 123);
// 执行index文件
node index.js // 没有输出
NODE_DEBUG=foo-bar node index.js //FOO-BAR 18470: hi there, it's foo-bar [2333]
NODE_DEBUG如果希望输出多个section可以用逗号做分隔,同时NODE_DEBUG也支持通配符形式(node版本需要10)
NODE_DEBUG=fs,net,tls // 多个section
NODE_DEBUG=foo* // 通配符
上面的debuglog函数执行的时候支持占位符,其实底层使用的是util.format方法。
util.format
util.format用于占位符替换,不同类型的数据采用不同的占位符表示
%s | 字符串 |
---|---|
%d | 整数或浮点数 |
%i | 整数 |
%f | 浮点数 |
%j | JSON |
%o | Object(包括不可枚举的属性) |
%O | Object(不包括不可枚举的属性) |
%% | 输出% |
对Object格式化输出字符串的时候用到的其实是util.inspect方法,%o与%O的区别仅在于调用inspect方法时传入配置项有别
%o 传入util.inspect的配置项是 { showHidden: true, showProxy: true }
util.inspect
util.inspect用于对object做格式化字符串操作,并提供个性化配置项
const util = require('util');
var child = {
"name": "child",
"age": 18,
"friends": [
{
"name": "randal",
"age" : 19,
"friends": [
{
"name": "Kate",
"age": 18
}
]
}
],
"motto": "Now this is not the end. It is not even the beginning of the end. But it is, perhaps, the end of the beginning."
}
console.log(util.inspect(child, { compact: false, depth: null, breakLength: 80}));
compact 用于各属性独占一行显示
depth 用于控制显示层级,默认是2
breakLength用于一行文字的最大个数,超出换行
更详细的参数及释义参见官网api
废弃API
使用util.deprecate方法可以针对废弃的api在终端输出废弃的提示信息
const util = require('util');
const oldFn = () => {
console.log('old fn');
};
class oldClass {
constructor() {
console.log('old class');
}
}
const fn1 = util.deprecate(oldFn, 'deprecated fn');
const fn2 = util.deprecate(oldClass, 'deprecated class');
fn1();
new fn2();
// 输出
old fn
old class
(node:18001) DeprecationWarning: deprecated fn
(node:18001) DeprecationWarning: deprecated class
具体实现
function deprecate(fn, msg, code) { let warned = false; function deprecated(...args) { if (!warned) { warned = true; if (code !== undefined) { if (!codesWarned
) {
process.emitWarning(msg, 'DeprecationWarning', code, deprecated); // emit警告信息
codesWarned= true; // 避免同一errorCode重复提示
}
} else {
process.emitWarning(msg, 'DeprecationWarning', deprecated);
}
}
if (new.target) { // class类型
return Reflect.construct(fn, args, new.target);
}
return fn.apply(this, args); // 函数类型
}
return deprecated;
}与此相关的命令行配置项
命令行选项 | process属性 | |
---|---|---|
不输出警告信息 | --no-deprecation --no-warnings | noDeprecation |
输出详细的堆栈信息 | --trace-deprecation --trace-warnings | traceDeprecation |
抛出错误异常 | --throw-deperecation | throwDeprecation |
其他
除了前面提到的三方面的工具外,util.types下还提供了非常丰富的类型识别的方法(isGeneratorFunction、isWeakSet、isPromise、isArrayBuffer)等,以后在需要类型判断的时候可以考虑使用util模板提供的这些方法。