Node.js util模块解读
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[code]) { process.emitWarning(msg, 'DeprecationWarning', code, deprecated); // emit警告信息 codesWarned[code] = 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模板提供的这些方法。