Пошук уроків, статей та іншого контенту
Застосуєте допоміжні функції util для форматування, перевірки типів, успадкування та перетворення callback API.
utilВбудований модуль node:util містить невеликі допоміжні функції, які часто потрібні під час розробки Node.js-застосунків:
форматування повідомлень;
безпечне представлення об’єктів;
перевірка спеціальних типів;
сумісність із класичним успадкуванням через конструктори;
перетворення callback API на Promise API і навпаки.
Модуль не потрібно встановлювати окремо:
const util = require('node:util');У сучасному коді бажано використовувати префікс node: для вбудованих модулів Node.js.
util.format()Функція util.format() створює рядок на основі шаблону та додаткових аргументів.
const util = require('node:util');
const message = util.format(
'Користувач %s має %d повідомлення',
'Olena',
5
);
console.log(message);
// Користувач Olena має 5 повідомленняНайпоширеніші специфікатори:
%s — рядкове значення;
%d — число;
%i — ціле число;
%f — число з плаваючою крапкою;
%j — значення у форматі JSON;
%o — об’єкт із додатковими властивостями;
%O — об’єкт у форматі, подібному до util.inspect().
Якщо додаткових аргументів більше, ніж специфікаторів, вони додаються до результату:
const util = require('node:util');
console.log(util.format('Статус:', 'готово', 200));
// Статус: готово 200Якщо перший аргумент не є рядком, усі аргументи форматуються як значення:
const util = require('node:util');
console.log(util.format({ name: 'Olena', active: true }));
// { name: 'Olena', active: true }util.inspect()util.inspect() перетворює значення на зручне для читання представлення. Ця функція особливо корисна під час налагодження складних об’єктів.
const util = require('node:util');
const user = {
id: 42,
name: 'Olena',
settings: {
theme: 'dark',
notifications: true
}
};
console.log(util.inspect(user, {
depth: null,
colors: false,
compact: false
}));Основні параметри:
depth — максимальна глибина вкладених об’єктів;
colors — додавання ANSI-кольорів;
compact — компактність виведення;
showHidden — показ прихованих і неперелічуваних властивостей.
Значення depth: null дозволяє показати об’єкт на будь-якій глибині. За замовчуванням глибина обмежена.
На практиці console.log() уже використовує механізми форматування Node.js, тому для простих випадків окремий виклик util.inspect() не потрібен. Він корисний, коли необхідно явно налаштувати формат.
util.typesМодуль util.types містить функції для перевірки спеціальних JavaScript-типів і внутрішніх типів Node.js.
const util = require('node:util');
console.log(util.types.isDate(new Date()));
// true
console.log(util.types.isRegExp(/node/i));
// true
console.log(util.types.isMap(new Map()));
// true
console.log(util.types.isSet(new Set()));
// true
console.log(util.types.isPromise(Promise.resolve()));
// true
console.log(util.types.isNativeError(new TypeError('Некоректне значення')));
// trueДля звичайних типів краще використовувати стандартні засоби JavaScript:
const value = [1, 2, 3];
console.log(Array.isArray(value));
console.log(typeof value === 'string');
console.log(value === null);util.types потрібен тоді, коли треба відрізнити спеціальні типи:
const util = require('node:util');
const values = [
new ArrayBuffer(8),
new Uint8Array(4),
new Date(),
new Map(),
Promise.resolve(10)
];
for (const value of values) {
console.log({
isArrayBuffer: util.types.isArrayBuffer(value),
isTypedArray: util.types.isTypedArray(value),
isDate: util.types.isDate(value),
isMap: util.types.isMap(value),
isPromise: util.types.isPromise(value)
});
}Деякі перевірки є взаємопов’язаними:
util.types.isTypedArray(value) перевіряє типізовані масиви, наприклад Uint8Array;
util.types.isArrayBuffer(value) перевіряє саме ArrayBuffer;
util.types.isAnyArrayBuffer(value) також охоплює SharedArrayBuffer;
util.types.isPromise(value) перевіряє Promise, навіть якщо він ще не завершився.
У старому коді можна зустріти функції на кшталт:
util.isArray(value);
util.isDate(value);
util.isError(value);Такі функції застарілі. Замість них використовуйте:
Array.isArray(value);
util.types.isDate(value);
util.types.isNativeError(value);util.inherits()util.inherits() налаштовує успадкування між функціями-конструкторами. Це підхід, який використовувався до появи синтаксису class.
const util = require('node:util');
const { EventEmitter } = require('node:events');
function Logger(prefix) {
EventEmitter.call(this);
this.prefix = prefix;
}
util.inherits(Logger, EventEmitter);
Logger.prototype.write = function (message) {
const fullMessage = `[${this.prefix}] ${message}`;
console.log(fullMessage);
this.emit('written', fullMessage);
};
const logger = new Logger('APP');
logger.on('written', (message) => {
console.log('Подію отримано:', message);
});
logger.write('Сервер запущено');У цьому прикладі:
Logger викликає конструктор EventEmitter;
util.inherits(Logger, EventEmitter) додає прототипне успадкування;
екземпляр Logger отримує методи EventEmitter;
об’єкт може створювати та обробляти події.
У новому коді переважно використовують class extends:
const { EventEmitter } = require('node:events');
class Logger extends EventEmitter {
constructor(prefix) {
super();
this.prefix = prefix;
}
write(message) {
const fullMessage = `[${this.prefix}] ${message}`;
console.log(fullMessage);
this.emit('written', fullMessage);
}
}util.inherits() залишається корисною під час підтримки старих модулів або API, побудованих на функціях-конструкторах.
util.promisify()У Node.js callback API зазвичай використовує такий формат:
function operation(input, callback) {
// callback отримує помилку або результат
callback(error, result);
}Перший аргумент callback — помилка або null, другий — результат операції.
util.promisify() перетворює таку функцію на функцію, яка повертає Promise.
const { promisify } = require('node:util');
function getUser(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error('Ідентифікатор має бути додатним'));
return;
}
callback(null, {
id,
name: 'Olena'
});
}, 100);
}
const getUserAsync = promisify(getUser);
async function main() {
try {
const user = await getUserAsync(42);
console.log(user);
} catch (error) {
console.error('Помилка:', error.message);
}
}
main();Після виклику promisify():
успішний виклик callback(null, result) перетворюється на виконаний Promise;
виклик callback(error) перетворюється на відхилений Promise;
результат можна отримати через await;
помилки обробляються за допомогою try...catch.
thisЯкщо callback-функція є методом об’єкта, під час передачі її до promisify() можна втратити контекст this.
const { promisify } = require('node:util');
const counter = {
value: 10,
getValue(callback) {
callback(null, this.value);
}
};
const getValueAsync = promisify(counter.getValue).bind(counter);
async function main() {
console.log(await getValueAsync());
}
main();bind(counter) гарантує, що всередині getValue() значення this посилатиметься на об’єкт counter.
Якщо API вже повертає Promise, застосовувати до нього promisify() не потрібно.
util.callbackify()util.callbackify() виконує зворотне перетворення: функція, яка повертає Promise, стає функцією з callback.
const { callbackify } = require('node:util');
async function loadSettings() {
return {
theme: 'dark',
language: 'uk'
};
}
const loadSettingsWithCallback = callbackify(loadSettings);
loadSettingsWithCallback((error, settings) => {
if (error) {
console.error('Помилка:', error.message);
return;
}
console.log('Налаштування:', settings);
});Callback, створений callbackify(), має стандартну форму:
(error, value) => {}Якщо Promise відхиляється, помилка передається першим аргументом:
const { callbackify } = require('node:util');
async function loadData() {
throw new Error('Не вдалося завантажити дані');
}
const loadDataWithCallback = callbackify(loadData);
loadDataWithCallback((error, data) => {
if (error) {
console.error(error.message);
return;
}
console.log(data);
});callbackify() корисний, коли нова Promise-функція повинна працювати зі старим кодом, який очікує callback.
Нижче наведено приклад, який демонструє форматування, перевірку типів, успадкування та обидва напрямки перетворення API.
const util = require('node:util');
const { EventEmitter } = require('node:events');
function Repository(name) {
EventEmitter.call(this);
this.name = name;
}
util.inherits(Repository, EventEmitter);
Repository.prototype.findById = function (id, callback) {
setTimeout(() => {
if (!Number.isInteger(id) || id <= 0) {
callback(new TypeError('id має бути додатним цілим числом'));
return;
}
const record = {
id,
name: 'Olena',
createdAt: new Date()
};
this.emit('found', record);
callback(null, record);
}, 50);
};
const repository = new Repository('users');
repository.on('found', (record) => {
console.log(util.format(
'Знайдено запис #%d: %s',
record.id,
record.name
));
console.log('createdAt є датою:', util.types.isDate(record.createdAt));
});
const findByIdAsync = util.promisify(
repository.findById.bind(repository)
);
async function loadUser() {
const user = await findByIdAsync(7);
console.log(util.inspect(user, {
depth: null,
colors: false
}));
return user;
}
const loadUserWithCallback = util.callbackify(loadUser);
loadUserWithCallback((error, user) => {
if (error) {
console.error('Помилка:', error.message);
return;
}
console.log('Завантаження завершено для:', user.name);
});У прикладі:
util.inherits() додає Repository можливості EventEmitter;
util.promisify() перетворює findById() на Promise-функцію;
bind(repository) зберігає правильний контекст this;
util.callbackify() повертає callback-сумісну версію loadUser();
util.format() створює повідомлення;
util.inspect() показує об’єкт;
util.types.isDate() перевіряє тип властивості createdAt.
promisify() без callback-сумісного APIpromisify() очікує функцію, яка викликає callback у форматі (error, result).
Неправильний приклад:
const { promisify } = require('node:util');
const promiseFunction = async () => 42;
const converted = promisify(promiseFunction);Якщо функція вже повертає Promise, використовуйте її напряму.
thisНеправильно:
const converted = promisify(object.method);Якщо method використовує this, передавайте прив’язану функцію:
const converted = promisify(object.method.bind(object));Не використовуйте старі util.is*() для нових проєктів. Обирайте стандартні перевірки JavaScript або util.types.
Після promisify() помилка callback стає відхиленням Promise. Її потрібно обробити:
try {
const result = await convertedFunction();
} catch (error) {
console.error(error);
}util.format() і util.inspect()util.format() створює повідомлення за шаблоном;
util.inspect() показує внутрішнє представлення значення.
Для журналу подій частіше потрібен format(), а для налагодження об’єкта — inspect().
util.format() форматує рядки та значення за допомогою специфікаторів.
util.inspect() дає змогу налаштувати представлення складних об’єктів.
util.types містить перевірки для дат, Promise, Map, Set, типізованих масивів та інших спеціальних типів.
util.inherits() підтримує прототипне успадкування між функціями-конструкторами; у новому коді зазвичай використовують class extends.
util.promisify() перетворює callback API на Promise API.
util.callbackify() перетворює Promise-функцію на callback API.
Під час перетворення методів об’єкта потрібно зберігати контекст this.