为了账号安全,请及时绑定邮箱和手机立即绑定

Node.js util模块解读

标签:
Node.js

原文链接:https://segmentfault.com/a/1190000015115159?utm_source=index-hottest

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 // 多个sectionNODE_DEBUG=foo*  // 通配符

上面的debuglog函数执行的时候支持占位符,其实底层使用的是util.format方法。

util.format

util.format用于占位符替换,不同类型的数据采用不同的占位符表示

%s字符串
%d整数或浮点数
%i整数
%f浮点数
%jJSON
%oObject(包括不可枚举的属性)
%OObject(不包括不可枚举的属性)
%%输出%

对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-deperecationthrowDeprecation

其他

除了前面提到的三方面的工具外,util.types下还提供了非常丰富的类型识别的方法(isGeneratorFunction、isWeakSet、isPromise、isArrayBuffer)等,以后在需要类型判断的时候可以考虑使用util模板提供的这些方法。

参考资料

https://nodejs.org/dist/latest-v10.x/docs/api/util.html


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消