Вбудований модуль util (Utilities)
Вбудований модуль util (Utilities)
🎯 Мета лекції
- Опанувати функцію
util.promisify()для перетворення callback-based API у Promise-based. - Освоїти механізм інспектування об'єктів через
util.inspect()для налагодження складних структур даних. - Вивчити модуль
util.typesдля точної перевірки типів JavaScript та вбудованих об'єктів Node.js. - Зрозуміти роль
util.format()у форматуванні рядків та інтерполяції значень. - Навчитися застосовувати
util.callbackify()для зворотної сумісності з legacy-кодом.
🔑 Ключові терміни
- util: вбудований модуль Node.js із допоміжними функціями для роботи з асинхронністю, типами та форматуванням.
- Promisify: процес перетворення функції з callback у функцію, що повертає Promise.
- Inspect: процес глибокого перетворення об'єкта у читабельне рядкове представлення.
- Type Guards: механізм точної перевірки типів у runtime для попередження помилок.
- Format String: шаблонний рядок із заповнювачами (
%s,%d,%j) для підстановки значень.
Архітектурний контекст: від callback до Promise
Історично Node.js розвивався у часи, коли JavaScript ще не мав нативних Promise (вони з'явилися у стандарті ES2015). Тому більшість вбудованих API — від fs до crypto — проєктувалися навколо callback-паттерну. Однак із прийняттям Promise та async/await у сучасних проєктах стало критично важливим мати єдиний механізм перетворення legacy-коду.
Саме для цього Node.js надає модуль util — набір утилітарних функцій, які вирішують типові завдання інженера: міграція callback на Promise, налагодження складних об'єктів, перевірка типів за межами можливостей typeof, форматування діагностичних повідомлень.
util є вбудованим у Node.js починаючи з версії 0.3.0, проте ключові функції на кшталт promisify() з'явилися лише у версії 8.0.0 (2017 рік), коли екосистема почала масову міграцію на async/await.Імпорт модуля util
Модуль доступний у трьох варіантах імпорту залежно від системи модулів та стилю коду:
import util from 'node:util';
import { promisify, inspect, types } from 'node:util';
// Використання окремих функцій
const readFileAsync = promisify(fs.readFile);
const util = require('util');
const { promisify, inspect } = require('util');
// Весь модуль у змінній util
console.log(util.types.isPromise(Promise.resolve()));
import * as util from 'node:util';
import type { InspectOptions } from 'node:util';
// Типізований імпорт для налаштувань inspect
const options: InspectOptions = { depth: 5, colors: true };
node: перед назвою модуля є рекомендованою практикою у сучасному Node.js (версії 16+). Він чітко позначає вбудовані модулі та унеможливлює конфлікти з npm-пакетами, які можуть мати ідентичні назви.Перетворення callback у Promise: util.promisify()
Функція util.promisify() приймає callback-функцію у форматі Node.js (де останній аргумент — це callback (err, result) => void) і повертає нову функцію, яка повертає Promise.
Базовий синтаксис та механіка роботи
Припустимо, у нас є legacy-функція для читання файлу з callback-based API:
import fs from 'node:fs';
import { promisify } from 'node:util';
// Оригінальна callback-функція
fs.readFile('config.json', 'utf8', (err, data) => {
if (err) {
console.error('Помилка читання:', err);
return;
}
console.log('Дані:', data);
});
// Перетворення на Promise через promisify
const readFileAsync = promisify(fs.readFile);
// Тепер можна використовувати async/await
async function loadConfig(): Promise<string> {
try {
const data = await readFileAsync('config.json', 'utf8');
return data;
} catch (err) {
console.error('Помилка завантаження конфігурації:', err);
throw err;
}
}
promisify() перетворює лише функції, які дотримуються Node.js callback convention: callback(error, result). Якщо callback має іншу сигнатуру (наприклад, callback(result) без помилки), promisify() не працюватиме коректно.Приклад міграції власної callback-функції
Розглянемо створення функції для читання конфігурації з файлу у двох варіантах: спочатку callback-based, потім перетворену через promisify.
import fs from 'node:fs';
interface Config {
database: string;
port: number;
}
function readConfig(
filePath: string,
callback: (err: Error | null, config?: Config) => void
): void {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
callback(err);
return;
}
try {
const config = JSON.parse(data) as Config;
callback(null, config);
} catch (parseError) {
callback(parseError as Error);
}
});
}
// Виклик з callback
readConfig('./config.json', (err, config) => {
if (err) {
console.error('Помилка:', err);
return;
}
console.log('База даних:', config?.database);
});
import fs from 'node:fs';
import { promisify } from 'node:util';
interface Config {
database: string;
port: number;
}
// Оригінальна callback-функція
function readConfig(
filePath: string,
callback: (err: Error | null, config?: Config) => void
): void {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
callback(err);
return;
}
try {
const config = JSON.parse(data) as Config;
callback(null, config);
} catch (parseError) {
callback(parseError as Error);
}
});
}
// Перетворення на Promise
const readConfigAsync = promisify(readConfig);
// Використання з async/await
async function main() {
try {
const config = await readConfigAsync('./config.json');
console.log('База даних:', config.database);
console.log('Порт:', config.port);
} catch (err) {
console.error('Помилка завантаження:', err);
}
}
main();
::
Альтернатива: нативний fs.promises
Починаючи з Node.js 10.0.0, модуль fs надає вбудований Promise-based API через fs.promises, тому promisify(fs.readFile) не є необхідним у сучасних проєктах:
import { readFile } from 'node:fs/promises';
async function loadData(): Promise<void> {
const data = await readFile('data.txt', 'utf8');
console.log(data);
}
Однак promisify() залишається незамінним для:
- Сторонніх бібліотек з callback API (наприклад,
redis,mysql). - Власних legacy-функцій, які не мають Promise-версій.
- Динамічного перетворення функцій у runtime.
promisify() призведе до помилки або некоректної поведінки. Завжди перевіряйте, чи функція має вбудовану Promise-версію, перш ніж застосовувати promisify().Кастомізація promisify через util.promisify.custom
Деякі функції мають специфічну логіку, яка не вписується у стандартний callback-паттерн. Для таких випадків promisify() підтримує символ util.promisify.custom, який дозволяє визначити власну реалізацію Promise-версії функції.
import { promisify } from 'node:util';
interface User {
id: number;
name: string;
}
// Функція з нестандартною сигнатурою callback
function fetchUser(id: number, callback: (user: User) => void): void {
setTimeout(() => {
callback({ id, name: 'Олександр' });
}, 100);
}
// Додавання кастомної Promise-реалізації
fetchUser[promisify.custom] = (id: number): Promise<User> => {
return new Promise((resolve) => {
fetchUser(id, (user) => resolve(user));
});
};
// Тепер promisify використовує кастомну реалізацію
const fetchUserAsync = promisify(fetchUser);
async function main() {
const user = await fetchUserAsync(42);
console.log('Користувач:', user.name);
}
main();
util.promisify.custom є стандартним механізмом у Node.js для розширення promisify(). Якщо ви розробляєте бібліотеку з callback API, додайте підтримку цього символу для зручності користувачів, які працюють з async/await.Зворотне перетворення: util.callbackify()
Функція util.callbackify() виконує зворотну операцію: приймає функцію, що повертає Promise, і перетворює її на callback-based функцію. Це корисно для забезпечення зворотної сумісності у бібліотеках, які мають підтримувати як старий, так і новий код.
import { callbackify } from 'node:util';
// Сучасна async-функція
async function fetchData(url: string): Promise<string> {
const response = await fetch(url);
return response.text();
}
// Перетворення на callback-версію
const fetchDataCallback = callbackify(fetchData);
// Виклик у legacy-коді
fetchDataCallback('https://api.example.com/data', (err, result) => {
if (err) {
console.error('Помилка:', err);
return;
}
console.log('Дані:', result);
});
callbackify() автоматично перетворює відхилені Promise у перший аргумент callback (помилку), а успішний результат — у другий аргумент. Це забезпечує повну сумісність із Node.js callback convention.Інспектування об'єктів: util.inspect()
Функція util.inspect() перетворює JavaScript-об'єкти у детальне рядкове представлення, враховуючи глибоку вкладеність, циклічні посилання та спеціальні типи даних. Це незамінний інструмент для налагодження складних структур, які console.log() не може відобразити повністю.
Базове використання та відмінності від console.log
Стандартний console.log() під капотом використовує util.inspect(), але з обмеженими налаштуваннями. Для точного контролю над форматуванням краще використовувати inspect() безпосередньо.
import { inspect } from 'node:util';
const complexObject = {
user: {
id: 42,
profile: {
name: 'Марія',
settings: {
theme: 'dark',
notifications: {
email: true,
push: false,
},
},
},
},
metadata: new Map([
['created', new Date('2025-01-15')],
['updated', new Date('2025-02-01')],
]),
buffer: Buffer.from('Node.js'),
};
// Стандартний console.log обріже глибокі об'єкти
console.log(complexObject);
// { user: { id: 42, profile: { name: 'Марія', settings: [Object] } }, ... }
// inspect з повною глибиною та кольорами
console.log(inspect(complexObject, { depth: null, colors: true }));
Налаштування InspectOptions
Функція inspect() приймає об'єкт конфігурації InspectOptions з десятками параметрів для точного керування виводом:
import { inspect } from 'node:util';
import type { InspectOptions } from 'node:util';
const data = {
users: [
{ id: 1, name: 'Іван', roles: ['admin', 'editor'] },
{ id: 2, name: 'Олена', roles: ['viewer'] },
],
circular: {} as any,
};
// Створення циклічного посилання
data.circular.self = data.circular;
const options: InspectOptions = {
depth: 3, // Глибина вкладеності (null = нескінченність)
colors: true, // ANSI-кольори у терміналі
compact: false, // Багаторядковий формат (не стиснутий)
breakLength: 60, // Максимальна довжина рядка перед переносом
sorted: true, // Сортування ключів об'єкта за алфавітом
getters: true, // Показувати значення getter-властивостей
showHidden: false, // Показувати неенумеровані властивості
maxArrayLength: 100, // Максимальна кількість елементів масиву
maxStringLength: 1000, // Обрізати довгі рядки
};
console.log(inspect(data, options));
Коли об'єкт містить посилання на самого себе (циклічна структура), inspect() використовує мітку <ref *N> для позначення повторюваних об'єктів. Це запобігає нескінченному циклу при виводі та дозволяє зрозуміти граф зв'язків між об'єктами.
Приклад:
const obj = { name: 'Root' } as any;
obj.self = obj;
console.log(inspect(obj, { depth: 2 }));
// <ref *1> { name: 'Root', self: [Circular *1] }
Можна визначити метод [Symbol.for('nodejs.util.inspect.custom')] у класі, який повертає рядкове представлення об'єкта. Цей метод буде автоматично викликатися inspect().
class User {
constructor(public id: number, public name: string) {}
[Symbol.for('nodejs.util.inspect.custom')](): string {
return `User<${this.id}>: ${this.name}`;
}
}
const user = new User(42, 'Андрій');
console.log(inspect(user)); // User<42>: Андрій
Перевірка типів: util.types
Стандартний оператор typeof у JavaScript має обмежені можливості: він не розрізняє масиви та об'єкти, не визначає null коректно, не може перевірити вбудовані типи на кшталт Map, Set, Promise чи Buffer. Модуль util.types надає набір точних предикатів (type guards) для runtime-перевірки типів.
Обмеження typeof та instanceof
Розглянемо типові проблеми стандартних механізмів перевірки типів:
// Проблема 1: typeof null
console.log(typeof null); // "object" (баг у специфікації!)
// Проблема 2: typeof не розрізняє масиви
console.log(typeof [1, 2, 3]); // "object"
console.log(typeof { a: 1 }); // "object"
// Проблема 3: instanceof не працює між контекстами
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
const localArray = new Array();
console.log(localArray instanceof Array); // true
console.log(localArray instanceof iframeArray); // false (!)
instanceof перевіряє прототип об'єкта, тому він не працюватиме коректно для об'єктів, створених у різних контекстах виконання (наприклад, між різними VM-модулями або iframe у браузері). Для надійних перевірок використовуйте util.types.Каталог util.types методів
Модуль util.types містить понад 30 предикатів для точної ідентифікації типів. Ось найважливіші з них:
import { types } from 'node:util';
// Перевірка примітивних обгорток
console.log(types.isBoxedPrimitive(new String('hello'))); // true
console.log(types.isBoxedPrimitive('hello')); // false
// Перевірка Promise
console.log(types.isPromise(Promise.resolve(42))); // true
console.log(types.isPromise({ then: () => {} })); // false (thenable, але не Promise)
// Перевірка вбудованих колекцій
console.log(types.isMap(new Map())); // true
console.log(types.isSet(new Set())); // true
console.log(types.isWeakMap(new WeakMap())); // true
// Перевірка типізованих масивів
console.log(types.isTypedArray(new Uint8Array(10))); // true
console.log(types.isUint8Array(new Uint8Array(10))); // true
console.log(types.isFloat64Array(new Float64Array(5))); // true
// Перевірка спеціальних об'єктів Node.js
console.log(types.isDate(new Date())); // true
console.log(types.isRegExp(/test/)); // true
console.log(types.isNativeError(new Error('fail'))); // true
console.log(types.isAsyncFunction(async () => {})); // true
console.log(types.isGeneratorFunction(function* () {})); // true
Практичний приклад: валідація вхідних даних API
Функція util.types незамінна при валідації даних, отриманих із зовнішніх джерел, де типи можуть бути непередбачуваними:
import { types } from 'node:util';
interface UserPayload {
id: number;
name: string;
metadata: Map<string, unknown>;
createdAt: Date;
}
function validateUserPayload(input: unknown): input is UserPayload {
if (typeof input !== 'object' || input === null) {
return false;
}
const payload = input as Record<string, unknown>;
// Перевірка типів через util.types
return (
typeof payload.id === 'number' &&
typeof payload.name === 'string' &&
types.isMap(payload.metadata) &&
types.isDate(payload.createdAt)
);
}
// Тестування функції
const validPayload = {
id: 42,
name: 'Ольга',
metadata: new Map([['role', 'admin']]),
createdAt: new Date(),
};
const invalidPayload = {
id: '42', // Рядок замість числа
name: 'Ольга',
metadata: { role: 'admin' }, // Object замість Map
createdAt: '2025-02-01', // Рядок замість Date
};
console.log(validateUserPayload(validPayload)); // true
console.log(validateUserPayload(invalidPayload)); // false
input is Type називаються type predicates і дозволяють компілятору звужувати тип змінної після перевірки. Це забезпечує type safety у runtime без додаткових приведень типів.Перевірка проксі-об'єктів та WebAssembly
Модуль util.types також підтримує сучасні JavaScript-конструкції:
import { types } from 'node:util';
// Proxy
const target = { value: 42 };
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`Доступ до ${String(prop)}`);
return obj[prop as keyof typeof obj];
},
});
console.log(types.isProxy(proxy)); // true
console.log(types.isProxy(target)); // false
// WebAssembly Module
const wasmCode = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
const wasmModule = new WebAssembly.Module(wasmCode);
console.log(types.isWebAssemblyCompiledModule(wasmModule)); // true
isProxy() працює лише для об'єктів, створених через конструктор Proxy. Вона не може виявити "приховані" проксі, створені всередині сторонніх бібліотек, оскільки JavaScript не надає рефлексію для інспектування проксі-ланцюгів.Форматування рядків: util.format()
Функція util.format() надає printf-подібний синтаксис для форматування рядків із підстановкою значень. Вона використовується всередині console.log(), але також доступна для ручного форматування.
Синтаксис заповнювачів
import { format } from 'node:util';
// %s — рядок (string)
console.log(format('Користувач: %s', 'Дмитро'));
// Користувач: Дмитро
// %d — число (decimal/integer)
console.log(format('Кількість: %d', 42));
// Кількість: 42
// %i — ціле число (integer)
console.log(format('ID: %i', 3.14));
// ID: 3 (округлення до цілого)
// %f — число з плаваючою комою (float)
console.log(format('Ціна: %f грн', 123.456));
// Ціна: 123.456 грн
// %j — JSON-серіалізація
console.log(format('Дані: %j', { user: 'Анна', age: 28 }));
// Дані: {"user":"Анна","age":28}
// %o — inspect() для об'єктів
const complexObj = { nested: { value: [1, 2, 3] } };
console.log(format('Об\'єкт: %o', complexObj));
// Об'єкт: { nested: { value: [ 1, 2, 3 ] } }
// %O — inspect() з опціями (глибше інспектування)
console.log(format('Деталі: %O', complexObj));
// %c — ігнорується (підтримка browser console.log CSS)
console.log(format('Текст %c', 'color: red'));
// Текст (CSS ігнорується у Node.js)
// %% — літеральний символ %
console.log(format('Прогрес: 50%%'));
// Прогрес: 50%
Практичний приклад: логування з контекстом
import { format } from 'node:util';
interface LogContext {
timestamp: Date;
level: 'INFO' | 'WARN' | 'ERROR';
userId?: number;
}
function log(context: LogContext, message: string, ...args: unknown[]): void {
const formattedMessage = format(message, ...args);
const contextStr = format(
'[%s] [%s] %s',
context.timestamp.toISOString(),
context.level,
context.userId ? `User ${context.userId}` : 'System'
);
console.log(`${contextStr} ${formattedMessage}`);
}
// Використання
log(
{ timestamp: new Date(), level: 'INFO', userId: 42 },
'Завантажено %d файлів за %f секунд',
15,
2.34
);
// Вивід:
// [2026-09-02T12:00:00.000Z] [INFO] User 42 Завантажено 15 файлів за 2.34 секунд
Додаткові утиліти модуля util
Модуль util містить кілька менш популярних, але корисних функцій для специфічних завдань.
util.deprecate(): позначення застарілого коду
Функція deprecate() обгортає функцію та виводить попередження при першому виклику. Це стандартний механізм для позначення API, що застаріває.
import { deprecate } from 'node:util';
// Стара версія функції
function oldCalculate(x: number, y: number): number {
return x + y;
}
// Обгортка з попередженням
const calculate = deprecate(
oldCalculate,
'calculate() застаріла. Використовуйте add() замість неї.',
'DEP0001' // Код deprecation (опціонально)
);
// Перший виклик виведе попередження у stderr
console.log(calculate(5, 3));
// DeprecationWarning: calculate() застаріла. Використовуйте add() замість неї.
// (Node.js:12345) [DEP0001] DeprecationWarning
// Наступні виклики не виводять попередження
console.log(calculate(10, 20));
--trace-deprecation.util.inherits(): класичне наслідування (Legacy)
Функція inherits() забезпечувала прототипне наслідування до появи класів ES2015. У сучасному коді вона застаріла, але зустрічається у legacy-бібліотеках.
import { inherits } from 'node:util';
import { EventEmitter } from 'node:events';
// Legacy-спосіб (ES5)
function MyStream() {
EventEmitter.call(this);
}
inherits(MyStream, EventEmitter);
MyStream.prototype.write = function (data: string) {
this.emit('data', data);
};
// Сучасний спосіб (ES2015+)
class ModernStream extends EventEmitter {
write(data: string): void {
this.emit('data', data);
}
}
util.inherits() у новому коді не рекомендується. Завжди віддавайте перевагу синтаксису class та extends, який є стандартом ECMAScript та підтримується TypeScript нативно.util.TextEncoder та util.TextDecoder: робота з Unicode
Класи TextEncoder та TextDecoder надають стандартний Web API для перетворення рядків у байтові масиви (UTF-8) та назад.
import { TextEncoder, TextDecoder } from 'node:util';
// Кодування рядка у Uint8Array
const encoder = new TextEncoder();
const encoded = encoder.encode('Привіт, Світ! 🌍');
console.log(encoded);
// Uint8Array(23) [
// 208, 159, 209, 128, 208, 184, 208, 178,
// 209, 150, 209, 130, ...
// ]
console.log('Розмір у байтах:', encoded.byteLength); // 23 байти (UTF-8)
// Декодування Uint8Array назад у рядок
const decoder = new TextDecoder('utf-8');
const decoded = decoder.decode(encoded);
console.log(decoded); // Привіт, Світ! 🌍
TextEncoder завжди кодує у UTF-8 і не підтримує інші кодування. Для декодування можна вказати кодування у конструкторі TextDecoder, включаючи utf-8, utf-16le, windows-1252, iso-8859-1 тощо.Приклад декодування з іншим кодуванням:
import { TextDecoder } from 'node:util';
// Байтовий масив у кодуванні Windows-1251 (кирилиця)
const win1251Bytes = new Uint8Array([207, 240, 232, 226, 179, 242]);
const decoder = new TextDecoder('windows-1251');
const text = decoder.decode(win1251Bytes);
console.log(text); // Привіт (якщо байти у Windows-1251)
util.getSystemErrorName(): декодування системних помилок
Функція getSystemErrorName() перетворює числовий код помилки операційної системи у символічне ім'я (наприклад, ENOENT, EACCES).
import { getSystemErrorName } from 'node:util';
import fs from 'node:fs';
try {
fs.readFileSync('/nonexistent/file.txt');
} catch (err: any) {
console.log('Код помилки:', err.errno); // -2
console.log('Назва:', getSystemErrorName(err.errno)); // ENOENT
console.log('Опис:', err.message);
// ENOENT: no such file or directory, open '/nonexistent/file.txt'
}
Це корисно для логування та обробки помилок на рівні операційної системи, особливо при роботі з файлами, сокетами та процесами.
Архітектурні паттерни застосування util
Міграція legacy-проєкту на async/await
Типовий сценарій у великих кодових базах: поступова заміна callback-функцій на Promise-based API без переписування всього коду одразу.
import { promisify } from 'node:util';
import fs from 'node:fs';
import crypto from 'node:crypto';
// Legacy callback-функції
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const randomBytes = promisify(crypto.randomBytes);
// Нова async-функція, що використовує старі API
async function processSecureFile(inputPath: string, outputPath: string): Promise<void> {
// Читання файлу
const content = await readFile(inputPath, 'utf8');
// Генерація ключа шифрування
const key = await randomBytes(32);
// Шифрування (спрощений приклад)
const encrypted = Buffer.from(content).toString('base64');
// Запис результату
await writeFile(outputPath, encrypted, 'utf8');
console.log(`Файл оброблено: ${inputPath} → ${outputPath}`);
console.log(`Ключ: ${key.toString('hex')}`);
}
// Виклик
processSecureFile('./data.txt', './data.encrypted.txt')
.catch(err => console.error('Помилка обробки:', err));
lib/promisified.ts, де зберігайте всі перетворені функції. Це дозволить уникнути дублювання викликів promisify() у різних частинах коду.Створення діагностичного логера з inspect
Реалізація логера, що автоматично форматує складні об'єкти:
import { inspect, format } from 'node:util';
import type { InspectOptions } from 'node:util';
class Logger {
private inspectOptions: InspectOptions = {
depth: 5,
colors: true,
compact: false,
breakLength: 80,
};
info(message: string, ...args: unknown[]): void {
const formatted = format(message, ...this.formatArgs(args));
console.log(`[INFO] ${formatted}`);
}
error(message: string, error: Error, context?: Record<string, unknown>): void {
console.error(`[ERROR] ${message}`);
console.error(' Stack:', error.stack);
if (context) {
console.error(' Context:', inspect(context, this.inspectOptions));
}
}
debug(label: string, data: unknown): void {
console.debug(`[DEBUG] ${label}:`);
console.debug(inspect(data, this.inspectOptions));
}
private formatArgs(args: unknown[]): unknown[] {
return args.map(arg => {
if (typeof arg === 'object' && arg !== null) {
return inspect(arg, { ...this.inspectOptions, colors: false });
}
return arg;
});
}
}
// Використання
const logger = new Logger();
logger.info('Сервер запущено на порту %d', 3000);
logger.debug('Конфігурація бази даних', {
host: 'localhost',
port: 5432,
pool: { min: 2, max: 10 },
});
try {
throw new Error('Втрачено з\'єднання з БД');
} catch (err) {
logger.error('Критична помилка', err as Error, {
timestamp: new Date(),
userId: 42,
});
}
Питання для самоконтролю
Так, але потрібно зберегти контекст this через .bind() або стрілкову функцію:
import { promisify } from 'node:util';
class Database {
query(sql: string, callback: (err: Error | null, rows?: any[]) => void): void {
setTimeout(() => callback(null, [{ id: 1 }]), 100);
}
}
const db = new Database();
// Правильний спосіб
const queryAsync = promisify(db.query.bind(db));
// Або через стрілкову функцію
const queryAsync2 = promisify((sql, cb) => db.query(sql, cb));
await queryAsync('SELECT * FROM users');
Без .bind() метод втратить доступ до this та викине помилку при спробі звернутися до властивостей класу.
За замовчуванням inspect() обмежує глибину вкладеності до 2 рівнів. Для повного відображення встановіть depth: null:
const deepObject = { a: { b: { c: { d: { e: 'значення' } } } } };
console.log(inspect(deepObject)); // { a: { b: { c: [Object] } } }
console.log(inspect(deepObject, { depth: null })); // Повна структура
Також можна глобально змінити поведінку через util.inspect.defaultOptions:
import { inspect } from 'node:util';
inspect.defaultOptions.depth = null;
Використовуйте util.types.isPromise():
import { types } from 'node:util';
const promise = Promise.resolve(42);
const thenable = { then: (resolve) => resolve(42) };
console.log(types.isPromise(promise)); // true
console.log(types.isPromise(thenable)); // false
// typeof/instanceof не допоможуть:
console.log(typeof promise); // "object"
console.log(promise instanceof Promise); // true (але не працює між контекстами)
Це критично при обробці значень із зовнішніх бібліотек, які можуть повертати Promise-like об'єкти замість справжніх Promise.
promisify() додає мінімальні накладні витрати — одне додаткове обгортання функції. У більшості випадків це незначне порівняно з самою операцією введення/виведення.
Benchmark (орієнтовно):
- Прямий callback: ~100% швидкості
- Через promisify: ~95-98% швидкості
- Нативний fs.promises: ~100% швидкості (без обгортки)
Для критичних за продуктивністю ділянок (наприклад, обробка мільйонів операцій у секунду) використовуйте нативні Promise API, якщо вони доступні. Для типового веб-сервера різниця непомітна.
Підсумок: коли використовувати util
Модуль util — це інструментальний пояс (toolbelt) для вирішення рутинних завдань у Node.js. Основні сценарії застосування:
🔄 Міграція коду
- Перетворення callback на Promise через
promisify() - Поступова модернізація legacy-проєктів
- Забезпечення зворотної сумісності через
callbackify()
🐛 Налагодження
- Інспектування складних об'єктів через
inspect() - Візуалізація циклічних структур та глибокої вкладеності
- Кастомний вивід для класів через
[util.inspect.custom]
🔍 Перевірка типів
- Точна runtime-ідентифікація типів через
util.types - Валідація даних API без сторонніх бібліотек
- Перевірка Proxy, Promise, Map, Buffer тощо
📝 Форматування
- Printf-стиль форматування через
format() - Створення структурованих логів
- Інтерполяція значень у діагностичні повідомлення
util часто недооцінюється розробниками, які встановлюють сторонні пакети для завдань, які вже вирішені у стандартній бібліотеці. Перш ніж додавати залежність, перевірте, чи util не надає потрібну функціональність.Ключовий принцип роботи з util — використовувати його як прошарок сумісності та діагностики, але не покладатися на нього для бізнес-логіки. Функції на кшталт promisify() є мостом до сучасного коду, а inspect() та types — інструментами для розуміння поведінки системи у runtime.
У наступних лекціях ми застосуємо ці знання для роботи зі змінними оточення, інструментами налагодження та оптимізації продуктивності Node.js-застосунків.