В последнее время я занимался общим исследованием основных модулей Node.js и недавно взглянул на модуль util. Я видел, как его использовали здесь и там в прошлом, но никогда особо не исследовал его. Я обнаружил несколько особых вариантов использования, но удобных функций. Вот краткое изложение каждого с некоторыми пояснениями и примерами. Как всегда, официальная документация тоже хороша.

В документации отмечается, что util поддерживается для внутреннего использования, однако инструменты в нем достаточно общие, чтобы они также могли принести пользу разработчикам приложений и модулей. Я видел в NPM множество пакетов, которые удовлетворяют схожие потребности, поэтому всегда интересно посмотреть, что можно сделать, используя основные модули и избегая лишних зависимостей.

Вот полный список util функций (без учета устаревшего):

  • util.debuglog: простое средство ведения журнала отладки, которое можно включить / отключить с помощью переменной среды.
  • util.deprecate: Оберните общедоступные функции простым уведомлением о прекращении поддержки.
  • util.format: средство форматирования строк (ala. sprintf, printf и т. д.)
  • util.inherits: обновляет прототип объекта, чтобы он унаследовал от другого.
  • util.inspect: более эффективная регистрация данных с поддержкой цвета, обхода глубины объекта и других параметров.

util.debuglog

Ведение журнала - одна из тех вещей, которая может легко выйти из-под контроля, иногда случайно совершаться и может раздражать, когда вы этого не хотите. util.debuglog предоставляет простую функцию ведения журнала, которая явно включается с помощью переменной среды NODE_DEBUG. Вот небольшой пример ...

const util = require('util');
const log = util.debuglog('API');
const myObject = {
  url: 'http://eran.sh',
  coffee: true,
  address: {
    address1: '123 Fake St.',
    address2: 'Gainesville, FL',
  },
  tags: ['red', 'banana', 'golf']
};
log(myObject);

Перед регистрацией чего-либо вызывается util.debuglog, которому присваивается тег, соответствующий модулю, в котором он будет использоваться. Затем он возвращает функцию, которую можно вызвать для регистрации чего-либо в вашем приложении.

По умолчанию, ничего не будет регистрироваться выше, поскольку util.debuglog по умолчанию молчит. Только когда приведенный выше сценарий будет запущен с NODE_DEBUG=API node app.js, мы действительно увидим вывод в консоли. Это позволяет оставить полезный журнал отладки в коде, не загромождая консоль и не влияя непреднамеренно на производительность.

После того, как переменная среды установлена, ведение журнала будет выглядеть примерно так:

Помимо имени модуля также отображается идентификатор процесса. Это может быть полезно при работе с несколькими процессами.

Если вам кажется, что util.debuglog слишком ограничен, существует множество более сложных решений для ведения журнала. У меня были хорошие результаты с winston, но есть и многие другие.

Подробнее на util.debuglog здесь.

util.deprecate

При создании приложения или инструмента, на которые полагаются другие разработчики, отказ от рекомендаций является необходимой практикой, которая позволяет вам, сопровождающему, продолжать развивать API вашего инструмента, не удаляя при этом случайно что-то, от чего зависят другие. util.deprecate настолько просто, насколько возможно. Допустим, у вас есть пакет на NPM, который предоставляет разработчикам функцию:

function capitalizeAndJoin(inputArray = []) {
  return inputArray
    .map(s => s.toString().toUpperCase())
    .join(',')
}
module.exports = capitalizeAndJoin;

Вот код нашего разработчика, использующий этот модуль:

const capitalizeAndJoin = require(‘./capitalize-and-join’);
const demoArray = [‘hello’, ‘test’, ‘wee’, 8];
console.log(capitalizeAndJoin(demoArray))
// Outputs:
// HELLO,TEST,WEE,8

Если мы решили отказаться от функции capitalizeAndJoin, мы можем легко это сделать с помощью util.deprecate.

const util = require('util');
// ...
module.exports = util.deprecate(
  capitalizeAndJoin, 
  'This function has been deprecated. Use something else instead.'
);

Обернув нашу функцию util.deprecate и предоставив предупреждающее сообщение, разработчики получат четкое указание на то, когда функция может быть удалена. Вот как это выглядит:

Программа по-прежнему работает в обычном режиме, однако отображается DeprecationWarning.

Подробнее на util.deprecate здесь.

util.format

Если вы знакомы с PHP или другими языками, которые включают утилиты форматирования строк, отсутствие функции printf в JavaScript может вам не понравиться. К счастью, Node.js предоставляет эту функцию через util.format для простоты использования. Пример вполне может говорить сам за себя:

const util = require('util');
const name = 'Eran';
const daysLeft = 4;
const message = util.format(
  'Hello, %s! You have %d days remaining on your subscription.',
  name,
  daysLeft
);
console.log(message);
// Outputs:
// Hello, Eran! You have 4 days remaining on your subscription.

util.format принимает строку формата, которая может включать токены, такие как %s, %d и некоторые другие. Это позволяет записывать строку без непосредственной интерполяции переменных. Это означает, что строка может храниться отдельно или извлекаться из локализованной группы сообщений, сохраняя при этом ваши переменные отдельно.

Подробнее на util.format здесь.

util.inherits

В мире до классов ES2015 (любите их или ненавижу их) наследование было чем-то, что нужно было делать вручную в JavaScript. Хотя это требовалось не для всего, это был шаблон, который иногда поощрялся в Node.js. Например, при написании эмиттера событий необходимо было импортировать Node.js EventEmitter, а затем наследовать от него для собственного использования. Обычно это выглядит так:

const util = require('util');
const EventEmitter = require('events');

function MyStream() {
  EventEmitter.call(this);
}

util.inherits(MyStream, EventEmitter);

Теперь, когда JavaScript поддерживает наследование в традиционном смысле класса ООП, рекомендуемым способом наследования от суперкласса является использование extends без необходимости util.inherits

const EventEmitter = require('events');

class MyStream extends EventEmitter {
  constructor() {
    super();
  }
}

Подробнее на util.inherits здесь.

util.inspect

Мы заканчиваем еще одним инструментом отладки, util.inspect аналогичен использованию console.log для регистрации структур данных. Он возвращает форматированное строковое представление для удобства использования человеком. Это мало чем отличается от вывода, который вы получаете при передаче объекта в console.log, однако он дает вам больше контроля. Ознакомьтесь с примерами ниже…

const util = require('util');
const myObject = {
  url: 'http://eran.sh',
  coffee: true,
  address: {
    address1: '123 Fake St.',
    address2: 'Gainesville, FL',
  },
  tags: ['red', 'banana', 'golf']
};
console.log(util.inspect(myObject))

Поведение по умолчанию даст вам что-то вроде этого:

Inspect принимает второй параметр, объект конфигурации, который позволяет настраивать вывод. Некоторые интересные предметы включают…

const inspectOpts = {
  showHidden: true, // Display non-enumerable properties
  depth: 1,         // How many nested properties deep should read
  colors: true,     // Display output in color
  breakLength: 1    // # of items to be listed on a single line
};

Примечание. Доступны дополнительные параметры, полный список см. в документации.

Возможность контролировать глубину возвращаемых данных может быть чрезвычайно полезной для предотвращения чрезмерного журнала и затруднения поиска данных, которые вы ищете, что делает util.inspect отличным инструментом в вашем распоряжении.

Подробнее на util.inspect здесь.

Единый регистратор для ленивых…

Чтобы довести некоторые из этих концепций отладки до логического завершения, давайте рассмотрим пример, в котором мы можем использовать util.debuglog и util.inspect для создания простого многоразового модуля для использования в нашем приложении.

simple-logger.js

const { debuglog, inspect } = require('util');
const defaultOptions = {
  depth: 1,          // How many nested properties deep should read
  colors: true,      // Display output in color
  breakLength: 20    // # of items to be listed on a single line
};
function getLogger(tagName = 'APP', overrideOptions = {}) {
  const logger = debuglog(tagName);
  const inspectOptions = Object.assign(
    {},
    defaultOptions,
    overrideOptions
  );
  return debugData => logger(inspect(debugData, inspectOptions))
}
module.exports = getLogger;

Теперь этот модуль можно импортировать в наш проект так…

const log = require('./simple-logger')();
const myObject = {
  // ...
};
log(myObject);

Теперь мы можем классифицировать тег util.debuglog, когда требуется модуль, или оставить его пустым, чтобы по умолчанию он был равен 'APP'. У нас также есть некоторые настройки конфигурации по умолчанию для util.inspect, чтобы добиться хорошего результата.

Использование подобного подхода позволяет вам безопасно оставить ведение журнала и использовать его только тогда, когда это необходимо, установив для переменной NODE_DEBUG значение 'APP'.

Спасибо за прочтение!

Это краткое изложение модуля util. Если вам интересно узнать больше о том, что можно сделать с помощью основных модулей Node.js, я определенно рекомендую ознакомиться с документацией по API. Документация не только хорошо написана и поддерживается, но и практически гарантировано, что вы будете узнавать что-то новое каждый раз, когда погружаетесь в нее. Оно того стоит!