Глобальні об\
Глобальні об'єкти Node.js
🎯 Мета лекції
- Зрозуміти відмінності глобального простору Node.js від браузерного середовища.
- Опанувати роботу з об'єктом
processдля доступу до метаданих процесу та змінних оточення. - Навчитися використовувати глобальні змінні
__dirname,__filenameта їх альтернативи в ES Modules. - Освоїти базові операції з бінарними даними через клас
Buffer. - Розібрати механізми планування асинхронних операцій:
setTimeout,setImmediate,process.nextTick.
🔑 Ключові терміни
- global / globalThis: глобальний простір імен Node.js, аналог
windowу браузері. - process: об'єкт, що представляє поточний процес Node.js з метаданими та методами управління.
- Buffer: клас для роботи з сирими бінарними даними поза купою V8 (heap).
- Event Loop Phases: фази циклу подій, що визначають порядок виконання таймерів та callbacks.
Глобальний простір Node.js vs Браузера
Відмінності у глобальному об'єкті
У браузерному JavaScript весь код виконується у контексті глобального об'єкта window. Саме цей об'єкт містить усі вбудовані API: document, localStorage, fetch, setTimeout тощо. Змінні, оголошені через var на глобальному рівні, а також функції через function declaration, автоматично стають властивостями window:
// Браузер
var globalVar = 'I am on window';
console.log(window.globalVar); // "I am on window"
function globalFunction() {
return 'Also on window';
}
console.log(window.globalFunction); // [Function: globalFunction]
// Неявна глобальна змінна (без var/let/const)
implicitGlobal = 'Dangerous!';
console.log(window.implicitGlobal); // "Dangerous!"
// let та const НЕ потрапляють у window
let blockScoped = 'Not on window';
const alsoBlockScoped = 'Not on window either';
console.log(window.blockScoped); // undefined
console.log(window.alsoBlockScoped); // undefined
Node.js, навпаки, не має об'єкта window, оскільки він не працює у контексті веб-сторінки. Замість цього Node.js надає об'єкт global, який виконує аналогічну роль — містить всі глобально доступні функції та об'єкти. Проте, на відміну від браузера, змінні верхнього рівня у Node.js не стають властивостями глобального об'єкта завдяки модульній системі:
// Node.js (CommonJS модуль)
const myVariable = 'Module-scoped';
console.log(global.myVariable); // undefined
// Явне додавання у глобальний простір (антипатерн!)
global.myVariable = 'Truly global';
console.log(global.myVariable); // "Truly global"
global.variableName = value є поганою практикою та може призвести до конфліктів імен у великих застосунках. Завжди використовуйте модульну систему для обміну даними між файлами.Уніфікований об'єкт globalThis
Для усунення фрагментації між середовищами (браузер, Node.js, Web Workers, Service Workers) стандарт ECMAScript 2020 ввів globalThis — уніфікований ідентифікатор глобального об'єкта, який працює скрізь:
console.log(globalThis === window); // true
console.log(globalThis === global); // true (до Node.js 21)
console.log(globalThis === self); // true
globalThis замість global або window для забезпечення переносимості коду між середовищами. Це особливо актуально для бібліотек, які можуть виконуватися як на сервері, так і у браузері (isomorphic/universal code).Що доступно глобально у Node.js
На відміну від браузера, Node.js не має DOM API (document, Element, HTMLElement), але надає власний набір глобальних об'єктів та функцій:
| Об'єкт/Функція | Призначення | Доступність |
|---|---|---|
console | Логування повідомлень у stdout/stderr | Глобально |
Buffer | Робота з бінарними даними | Глобально |
process | Інформація про поточний процес | Глобально |
setTimeout / setInterval | Таймери для відкладеного виконання | Глобально |
setImmediate / clearImmediate | Виконання після I/O фази Event Loop | Глобально (Node.js специфічно) |
__dirname / __filename | Шляхи до поточного модуля | Лише CommonJS |
require / module / exports | Модульна система CommonJS | Лише CommonJS |
import.meta | Метадані ES Module | Лише ES Modules |
process технічно не є властивістю global, але доступний у будь-якій точці програми без імпорту, оскільки Node.js автоматично інжектує його у контекст виконання.Об'єкт process: серце метаданих процесу
Об'єкт process — це один із найважливіших глобальних об'єктів Node.js. Він представляє поточний процес Node.js і надає величезну кількість корисної інформації та методів для взаємодії з середовищем виконання.
Інформація про середовище: версії та платформа
Кожен процес Node.js має доступ до метаданих про версії компонентів платформи та операційну систему:
console.log('Node.js version:', process.version); // v22.11.0
console.log('V8 version:', process.versions.v8); // 12.4.254.20-node.23
console.log('libuv version:', process.versions.uv); // 1.48.0
console.log('OpenSSL version:', process.versions.openssl); // 3.0.13+quic
console.log('Platform:', process.platform); // darwin, linux, win32
console.log('Architecture:', process.arch); // x64, arm64, x86
console.log('Process ID:', process.pid); // 42891
Ці властивості часто використовуються для умовної логіки, залежної від платформи. Наприклад, шляхи до системних бібліотек або команди оболонки відрізняються між Unix-подібними системами та Windows:
import path from 'node:path';
// Умовна логіка залежно від платформи
if (process.platform === 'win32') {
console.log('Running on Windows');
// Використання зворотних слешів у шляхах: C:\Users\...
} else if (process.platform === 'darwin') {
console.log('Running on macOS');
// Unix-подібні шляхи: /Users/...
} else if (process.platform === 'linux') {
console.log('Running on Linux');
// Unix-подібні шляхи: /home/...
}
// Умовна логіка залежно від архітектури
if (process.arch === 'arm64') {
console.log('Running on ARM64 architecture (Apple Silicon, Raspberry Pi)');
} else if (process.arch === 'x64') {
console.log('Running on x86-64 architecture (Intel/AMD)');
}
process.platform не завжди відповідає назві операційної системи. Наприклад, Windows повертає 'win32' навіть на 64-бітних системах, а macOS повертає 'darwin' — назву ядра операційної системи.Аргументи командного рядка: process.argv
Коли ви запускаєте Node.js скрипт, усі аргументи командного рядка стають доступними через масив process.argv. Перші два елементи завжди фіксовані:
process.argv[0]— повний шлях до виконуваного файлу Node.js (наприклад,/usr/local/bin/node)process.argv[1]— повний шлях до запущеного JavaScript-файлуprocess.argv[2]і далі — власне аргументи, передані скрипту
Розглянемо практичний приклад — утиліту для конвертації температури:
// temperature.js
const args = process.argv.slice(2); // Пропускаємо перші два елементи
if (args.length < 2) {
console.error('Usage: node temperature.js <value> <unit>');
console.error('Example: node temperature.js 25 celsius');
process.exit(1); // Код виходу 1 вказує на помилку
}
const value = parseFloat(args[0]);
const unit = args[1].toLowerCase();
if (isNaN(value)) {
console.error('Error: Temperature value must be a number');
process.exit(1);
}
switch (unit) {
case 'celsius':
case 'c':
const fahrenheit = (value * 9/5) + 32;
const kelvin = value + 273.15;
console.log(`${value}°C = ${fahrenheit.toFixed(2)}°F = ${kelvin.toFixed(2)}K`);
break;
case 'fahrenheit':
case 'f':
const celsius = (value - 32) * 5/9;
const kelvinFromF = celsius + 273.15;
console.log(`${value}°F = ${celsius.toFixed(2)}°C = ${kelvinFromF.toFixed(2)}K`);
break;
case 'kelvin':
case 'k':
const celsiusFromK = value - 273.15;
const fahrenheitFromK = (celsiusFromK * 9/5) + 32;
console.log(`${value}K = ${celsiusFromK.toFixed(2)}°C = ${fahrenheitFromK.toFixed(2)}°F`);
break;
default:
console.error(`Error: Unknown unit "${unit}". Use celsius, fahrenheit, or kelvin.`);
process.exit(1);
}
Приклад використання цього скрипту:
commander, yargs або вбудований (з Node.js 18.11) модуль node:util.parseArgs. Вони надають підтримку прапорців (flags), опцій зі значеннями та автоматичну генерацію довідки.Змінні оточення: process.env
Змінні оточення (environment variables) — це механізм передачі конфігурації у застосунок без жорсткого кодування (hardcoding) значень у вихідний код. У Node.js доступ до них здійснюється через об'єкт process.env.
// Доступ до змінної оточення
const port = process.env.PORT || 3000;
const nodeEnv = process.env.NODE_ENV || 'development';
const databaseUrl = process.env.DATABASE_URL;
console.log(`Starting server in ${nodeEnv} mode on port ${port}`);
if (!databaseUrl) {
console.error('ERROR: DATABASE_URL environment variable is not set');
process.exit(1);
}
Змінні оточення передаються при запуску процесу:
Для зручної роботи зі змінними оточення у розробці часто використовують файл .env та бібліотеку dotenv:
.env у систему контролю версій (Git). Додайте його у .gitignore. Секретні ключі та паролі мають зберігатися у безпечних сховищах, таких як HashiCorp Vault, AWS Secrets Manager або змінні оточення CI/CD платформи.Робочий каталог та шляхи: process.cwd() vs __dirname
Node.js надає кілька способів визначення шляхів до файлів та директорій, і важливо розуміти різницю між ними.
process.cwd() — повертає поточний робочий каталог (current working directory), з якого було запущено процес Node.js:
console.log('Current working directory:', process.cwd());
// /Users/arakviel/projects/my-app (залежить від місця запуску)
__dirname (лише CommonJS) — повертає абсолютний шлях до директорії, де знаходиться поточний модуль:
// Файл: /Users/arakviel/projects/my-app/src/utils/helper.js
console.log('Module directory:', __dirname);
// /Users/arakviel/projects/my-app/src/utils
console.log('Module file:', __filename);
// /Users/arakviel/projects/my-app/src/utils/helper.js
Ключова відмінність стає очевидною при запуску скрипту з різних каталогів:
Практичне застосування — читання конфігураційного файлу:
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename: string = fileURLToPath(import.meta.url);
const __dirname: string = path.dirname(__filename);
// ❌ НЕПРАВИЛЬНО: залежить від місця запуску
const configWrong = fs.readFileSync('config.json', 'utf8');
// ✅ ПРАВИЛЬНО: завжди шукає config.json у директорії модуля
const configPath = path.join(__dirname, 'config.json');
const configRight = fs.readFileSync(configPath, 'utf8');
// ✅ ТАКОЖ ПРАВИЛЬНО: шукає config.json у корені проєкту
const rootConfigPath = path.join(process.cwd(), 'config.json');
const rootConfig = fs.readFileSync(rootConfigPath, 'utf8');
type: "module" у package.json) змінні __dirname та __filename недоступні. Замість них використовуйте import.meta.url та модуль node:url:import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('Module file:', __filename);
console.log('Module directory:', __dirname);
Завершення процесу: process.exit()
Метод process.exit([code]) негайно припиняє виконання процесу Node.js з вказаним кодом виходу (exit code). За конвенцією Unix/Linux:
- 0 — успішне завершення без помилок.
- 1 або більше — завершення з помилкою (різні числа можуть вказувати на різні типи помилок).
import fs from 'node:fs';
try {
const data: string = fs.readFileSync('important-file.json', 'utf8');
const config = JSON.parse(data);
if (!config.apiKey) {
console.error('ERROR: apiKey is missing in configuration');
process.exit(1); // Код помилки 1
}
console.log('Configuration loaded successfully');
// Продовження роботи програми
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('FATAL ERROR:', message);
process.exit(2); // Код помилки 2 (інший тип помилки)
}
process.exit()негайно припиняє процес, не очікуючи завершення асинхронних операцій. Відкриті файлові дескриптори, мережеві з'єднання та незавершені записи у потоки можуть бути втрачені. Для коректного завершення використовуйте паттерн graceful shutdown:let isShuttingDown = false;
async function shutdown() {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('Shutting down gracefully...');
// Закриття сервера (перестати приймати нові з'єднання)
server.close(() => {
console.log('Server closed');
});
// Закриття з'єднання з базою даних
await database.close();
console.log('All connections closed. Exiting.');
process.exit(0);
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
Обробка сигналів операційної системи
Операційна система може надсилати процесу сигнали (signals) для керування його життєвим циклом. Node.js дозволяє перехоплювати ці сигнали через слухачі подій об'єкта process:
// SIGINT — Ctrl+C у терміналі
process.on('SIGINT', () => {
console.log('\nReceived SIGINT (Ctrl+C). Exiting gracefully...');
process.exit(0);
});
// SIGTERM — запит на завершення від менеджера процесів (Docker, Kubernetes, systemd)
process.on('SIGTERM', () => {
console.log('Received SIGTERM. Shutting down...');
process.exit(0);
});
// uncaughtException — необроблене виключення
process.on('uncaughtException', (error) => {
console.error('FATAL: Uncaught Exception', error);
process.exit(1);
});
// unhandledRejection — необроблений reject у Promise
process.on('unhandledRejection', (reason, promise) => {
console.error('FATAL: Unhandled Promise Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
uncaughtException та unhandledRejection є останнім рубежем оборони. Застосунок вже знаходиться у нестабільному стані, і продовження роботи може призвести до пошкодження даних. Після логування помилки рекомендується завершити процес та покластися на менеджер процесів (PM2, systemd, Kubernetes) для автоматичного перезапуску.Метрики процесу: пам'ять та час виконання
Об'єкт process надає корисні методи для моніторингу стану застосунку:
// Час роботи процесу у секундах
console.log('Uptime:', process.uptime(), 'seconds');
// Використання пам'яті
const memUsage = process.memoryUsage();
console.log('Memory Usage:');
console.log(' RSS:', (memUsage.rss / 1024 / 1024).toFixed(2), 'MB'); // Resident Set Size
console.log(' Heap Total:', (memUsage.heapTotal / 1024 / 1024).toFixed(2), 'MB');
console.log(' Heap Used:', (memUsage.heapUsed / 1024 / 1024).toFixed(2), 'MB');
console.log(' External:', (memUsage.external / 1024 / 1024).toFixed(2), 'MB'); // C++ об'єкти
// Використання CPU
const startUsage = process.cpuUsage();
// ... виконання коду ...
const endUsage = process.cpuUsage(startUsage);
console.log('CPU Usage:', {
user: endUsage.user, // Мікросекунди у user mode
system: endUsage.system // Мікросекунди у kernel mode
});
Ці метрики критичні для діагностики витоків пам'яті (memory leaks) та проблем продуктивності у продакшн-середовищі.
Об'єкт console: професійне логування
Об'єкт console у Node.js надає набір методів для виведення інформації у стандартні потоки виведення (stdout) та помилок (stderr). Хоча він схожий на браузерний console, серверна версія має деякі відмінності та додаткові можливості.
Базові методи логування
// Звичайне повідомлення → stdout
console.log('Server started successfully');
// Інформаційне повідомлення → stdout
console.info('Database connection established');
// Попередження → stderr
console.warn('Deprecated API usage detected');
// Помилка → stderr
console.error('Failed to connect to Redis');
console.log та console.info функціонально ідентичні — обидва пишуть у stdout. Різниця лише семантична: console.info краще передає намір логувати інформаційні події, а не тимчасові відлагоджувальні повідомлення.Форматування виведення
Console підтримує інтерполяцію рядків (string substitution) за аналогією з функцією printf у C:
const username = 'john_doe';
const userId = 42;
const loginTime = new Date();
// %s — рядок, %d/%i — ціле число, %f — число з плаваючою точкою, %o — об'єкт
console.log('User %s (ID: %d) logged in at %s', username, userId, loginTime.toISOString());
// User john_doe (ID: 42) logged in at 2026-09-02T14:30:00.000Z
const price = 19.99;
console.log('Total: $%f', price); // Total: $19.99
console.log('Total: $%.2f', price); // Total: $19.99 (заокруглення до 2 знаків)
const user = { id: 1, name: 'Alice', role: 'admin' };
console.log('User object: %o', user);
// User object: { id: 1, name: 'Alice', role: 'admin' }
Сучасніший підхід — шаблонні рядки (template literals):
console.log(`User ${username} (ID: ${userId}) logged in at ${loginTime.toISOString()}`);
Структуроване логування: console.table()
Метод console.table() відображає табличні дані у зручному для читання форматі:
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'moderator' }
];
console.table(users);
Можна вибірково показати лише певні колонки:
console.table(users, ['name', 'role']);
Вимірювання часу виконання: console.time()
Для профілювання продуктивності окремих ділянок коду використовуйте пару методів console.time() та console.timeEnd():
console.time('Database Query');
// Симуляція запиту до бази даних
setTimeout(() => {
console.timeEnd('Database Query');
// Database Query: 150.234ms
}, 150);
// Вкладені таймери
console.time('Total Operation');
console.time('Step 1: Validation');
// ... код валідації ...
console.timeEnd('Step 1: Validation');
console.time('Step 2: Processing');
// ... код обробки ...
console.timeEnd('Step 2: Processing');
console.timeEnd('Total Operation');
Для проміжних вимірювань без зупинки таймера використовуйте console.timeLog():
console.time('Download');
setTimeout(() => {
console.timeLog('Download', 'Downloaded 25%');
}, 250);
setTimeout(() => {
console.timeLog('Download', 'Downloaded 75%');
}, 750);
setTimeout(() => {
console.timeEnd('Download');
}, 1000);
Групування повідомлень: console.group()
Для структурування великих обсягів логів використовуйте групування:
console.log('Application started');
console.group('Database Configuration');
console.log('Host:', process.env.DB_HOST);
console.log('Port:', process.env.DB_PORT);
console.log('Database:', process.env.DB_NAME);
console.groupEnd();
console.group('API Configuration');
console.log('Base URL:', process.env.API_URL);
console.log('Timeout:', process.env.API_TIMEOUT);
console.group('Rate Limiting');
console.log('Max requests per minute:', 100);
console.log('Burst limit:', 20);
console.groupEnd(); // Закриття вкладеної групи
console.groupEnd(); // Закриття групи API Configuration
console.log('All services initialized');
Трасування стеку викликів: console.trace()
Метод console.trace() виводить повідомлення разом зі стеком викликів (stack trace), що допомагає зрозуміти, як виконання потрапило у певну точку коду:
function processOrder(orderId) {
validateOrder(orderId);
}
function validateOrder(orderId) {
if (orderId < 0) {
console.trace('Invalid order ID detected');
}
}
processOrder(-1);
console використовуйте професійні бібліотеки логування, такі як Winston, Pino або Bunyan. Вони надають структуроване логування у форматі JSON, рівні деталізації (log levels), ротацію файлів та інтеграцію із системами агрегації логів (ELK Stack, Datadog, Splunk).Клас Buffer: робота з бінарними даними
JavaScript історично був мовою для роботи з текстом та об'єктами у браузері, де бінарні дані майже не використовувалися. Проте серверні застосунки постійно працюють із сирими байтами: читання файлів, мережеві протоколи, потоки відео, криптографія. Для цих задач Node.js надає глобальний клас Buffer.
Що таке Buffer
Buffer — це послідовність байтів фіксованого розміру, що зберігається поза купою V8 (outside V8 heap). Це означає, що пам'ять для буферів виділяється безпосередньо операційною системою, а не керується збирачем сміття JavaScript. Завдяки цьому операції з великими обсягами бінарних даних виконуються ефективніше.
Кожен елемент буфера — це один байт (8 біт), значення якого знаходиться у діапазоні 0–255 (беззнакове ціле число, unsigned integer).
Створення буферів
1. Створення буфера з UTF-8 рядка:
const buffer1 = Buffer.from('Hello, Node.js!');
console.log(buffer1);
// <Buffer 48 65 6c 6c 6f 2c 20 4e 6f 64 65 2e 6a 73 21>
console.log(buffer1.length); // 15 (кількість байтів)
console.log(buffer1[0]); // 72 (код ASCII літери 'H')
2. Створення буфера з масиву байтів:
const buffer2 = Buffer.from([72, 101, 108, 108, 111]); // "Hello" у ASCII
console.log(buffer2.toString()); // "Hello"
3. Виділення порожнього буфера:
const buffer3 = Buffer.alloc(10); // 10 байтів, заповнених нулями
console.log(buffer3);
// <Buffer 00 00 00 00 00 00 00 00 00 00>
const buffer4 = Buffer.alloc(5, 'AB'); // Заповнення патерном 'AB'
console.log(buffer4);
// <Buffer 41 42 41 42 41> (ABABA)
4. Небезпечне виділення (без ініціалізації):
const buffer5 = Buffer.allocUnsafe(10);
console.log(buffer5);
// <Buffer 38 f2 7a 01 00 00 00 00 00 00> (випадкові дані з пам'яті!)
Buffer.allocUnsafe() не очищає виділену пам'ять, тому буфер може містити залишки попередніх даних з пам'яті процесу. Це швидше, ніж Buffer.alloc(), але потенційно небезпечно, якщо буфер використовується без подальшого заповнення. Використовуйте лише тоді, коли впевнені, що кожен байт буде перезаписаний перед читанням.Перетворення між кодуваннями
Buffer підтримує безліч текстових кодувань (encodings):
const text = 'Привіт, світ!';
// UTF-8 (за замовчуванням, підтримує кирилицю та емодзі)
const utf8Buffer = Buffer.from(text, 'utf8');
console.log('UTF-8:', utf8Buffer.length, 'bytes');
// UTF-8: 27 bytes (кирилиця займає 2 байти на символ)
// Base64 (кодування для передачі бінарних даних у текстовому форматі)
const base64 = utf8Buffer.toString('base64');
console.log('Base64:', base64);
// 0J/RgNC40LLRltGCLCDRgdCy0ZbRgiE=
const decodedBuffer = Buffer.from(base64, 'base64');
console.log('Decoded:', decodedBuffer.toString('utf8'));
// Decoded: Привіт, світ!
// Hexadecimal (шістнадцяткове представлення)
const hexString = utf8Buffer.toString('hex');
console.log('Hex:', hexString);
// d0bfd180d0b8d0b2d196d182...
Підтримувані кодування: 'utf8', 'utf16le', 'latin1', 'base64', 'base64url', 'hex', 'ascii', 'binary'.
Читання та запис даних у буфер
Buffer надає методи для роботи з різними типами даних:
const buffer = Buffer.alloc(8);
// Запис 32-бітного цілого числа (Little-Endian)
buffer.writeInt32LE(305419896, 0);
console.log(buffer);
// <Buffer 78 56 34 12 00 00 00 00>
// Читання 32-бітного цілого числа
const value = buffer.readInt32LE(0);
console.log(value); // 305419896
// Запис числа з плаваючою точкою (64-біт, double)
const floatBuffer = Buffer.alloc(8);
floatBuffer.writeDoubleLE(3.141592653589793, 0);
console.log(floatBuffer.readDoubleLE(0)); // 3.141592653589793
- Little-Endian (LE): молодший байт зберігається першим. Використовується у процесорах x86, x86-64, ARM.
- Big-Endian (BE): старший байт зберігається першим. Використовується у мережевих протоколах (Network Byte Order).
readInt32LE / readInt32BE, writeFloatLE / writeFloatBE тощо.Практичне застосування: читання файлу як Buffer
Модуль fs (file system) повертає вміст файлів як Buffer за замовчуванням:
import fs from 'node:fs';
// Читання файлу як Buffer
const imageBuffer: Buffer = fs.readFileSync('photo.jpg');
console.log('File size:', imageBuffer.length, 'bytes');
console.log('First 16 bytes:', imageBuffer.subarray(0, 16));
// Перевірка сигнатури файлу (Magic Number)
if (imageBuffer[0] === 0xFF && imageBuffer[1] === 0xD8) {
console.log('This is a JPEG image');
} else if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) {
console.log('This is a PNG image');
}
// Запис буфера у новий файл
fs.writeFileSync('photo-copy.jpg', imageBuffer);
Конкатенація буферів
Для об'єднання кількох буферів використовуйте статичний метод Buffer.concat():
const buf1 = Buffer.from('Hello, ');
const buf2 = Buffer.from('world!');
const buf3 = Buffer.from(' Welcome to Node.js.');
const combined = Buffer.concat([buf1, buf2, buf3]);
console.log(combined.toString());
// Hello, world! Welcome to Node.js.
console.log('Total size:', combined.length, 'bytes');
// Total size: 35 bytes
Buffer залишається зручним для серверних задач завдяки додатковим методам, таким як toString() з підтримкою різних кодувань.Візуалізація структури Buffer у пам'яті
Buffer Memory Layout
У цій візуалізації показано буфер з рядком "Hello, World!". Перші п'ять байтів (виділені кольором) відповідають ASCII-кодам літер H, e, l, l, o.
Таймери та планування асинхронних операцій
Node.js надає кілька механізмів для планування виконання коду з затримкою або у наступних ітераціях Event Loop. Розуміння різниці між setTimeout, setImmediate та process.nextTick критично важливе для написання передбачуваного асинхронного коду.
setTimeout та setInterval: відкладене виконання
Ці функції працюють аналогічно до браузерних версій, але реалізовані через libuv та інтегровані у Event Loop:
// setTimeout: одноразове виконання через затримку
const timeoutId = setTimeout(() => {
console.log('Executed after 2 seconds');
}, 2000);
// Скасування таймера
clearTimeout(timeoutId);
// setInterval: повторюване виконання з інтервалом
let counter = 0;
const intervalId = setInterval(() => {
counter++;
console.log(`Interval tick ${counter}`);
if (counter >= 5) {
clearInterval(intervalId);
console.log('Interval stopped');
}
}, 1000);
process.hrtime.bigint() або API performance з модуля node:perf_hooks.setImmediate: виконання після I/O фази
setImmediate() — це специфічна для Node.js функція, яка планує виконання callback після завершення поточної фази I/O у Event Loop:
console.log('1: Script start');
setTimeout(() => {
console.log('3: setTimeout callback');
}, 0);
setImmediate(() => {
console.log('4: setImmediate callback');
});
console.log('2: Script end');
Очікуваний порядок виконання:
Проте порядок між setTimeout(fn, 0) та setImmediate(fn) недетермінований при запуску у головному модулі через особливості ініціалізації Event Loop. У контексті I/O операцій setImmediate гарантовано виконується раніше:
import fs from 'node:fs';
fs.readFile(__filename, () => {
console.log('Inside I/O callback');
setTimeout(() => {
console.log('setTimeout inside I/O');
}, 0);
setImmediate(() => {
console.log('setImmediate inside I/O');
});
});
process.nextTick: найвищий пріоритет
process.nextTick() — це найпотужніший механізм планування у Node.js. Callbacks, додані через nextTick, виконуються перед будь-якою іншою фазою Event Loop, навіть перед таймерами та Promise-мікрозадачами:
console.log('1: Start');
setTimeout(() => console.log('4: setTimeout'), 0);
Promise.resolve().then(() => console.log('3: Promise'));
process.nextTick(() => {
console.log('2: nextTick');
});
console.log('1.5: Synchronous end');
Порядок пріоритетів у Event Loop:
process.nextTick(), рекурсивно викликає сам себе, Event Loop ніколи не перейде до наступної фази. Це призведе до блокування всіх інших операцій, включаючи таймери, мережеві події та завершення процесу:// ❌ АНТИПАТЕРН: блокування Event Loop
function recursiveNextTick() {
process.nextTick(recursiveNextTick);
}
recursiveNextTick();
// Process залипне, таймери не спрацюють
setTimeout(() => {
console.log('This will never execute');
}, 1000);
nextTick callbacks у одній ітерації (за замовчуванням 10000), але краще взагалі уникати рекурсивних викликів.Порівняння механізмів планування
| Механізм | Фаза Event Loop | Типовий Use Case |
|---|---|---|
setTimeout(fn, 0) | Timers Phase | Відкладення виконання до наступної ітерації Event Loop |
setImmediate(fn) | Check Phase (після I/O) | Виконання після завершення поточних I/O операцій |
process.nextTick(fn) | Перед будь-якою фазою | Виконання якомога швидше, до наступного тіка Event Loop |
Promise.then(fn) | Microtask Queue | Асинхронна логіка з підтримкою ланцюжків |
setTimeout— для симуляції затримок, ретраїв (retries), періодичних перевірок.setImmediate— для розбиття тривалих обчислень на частини без блокування I/O.process.nextTick— для виконання callback якомога швидше перед Event Loop, але дуже обережно.Promise— для більшості асинхронної логіки у сучасному коді (async/await).
Діаграма Event Loop з таймерами
Практичний приклад: розбиття тривалих обчислень
Одна з типових задач — обробка великого масиву даних без блокування Event Loop:
const hugeArray = Array.from({ length: 1000000 }, (_, i) => i);
function processBatch(data, batchSize, callback) {
let index = 0;
function processChunk() {
const endIndex = Math.min(index + batchSize, data.length);
// Обробка частини даних
for (let i = index; i < endIndex; i++) {
// Симуляція обчислень
data[i] = data[i] * 2;
}
index = endIndex;
if (index < data.length) {
// Відкладення наступної ітерації, щоб Event Loop міг обробити інші події
setImmediate(processChunk);
} else {
callback();
}
}
processChunk();
}
console.log('Processing started...');
processBatch(hugeArray, 10000, () => {
console.log('Processing complete!');
console.log('First element:', hugeArray[0]);
console.log('Last element:', hugeArray[hugeArray.length - 1]);
});
// Event Loop залишається вільним для інших операцій
setTimeout(() => {
console.log('This timer executes during processing');
}, 50);
Завдяки setImmediate обробка розбита на частини по 10,000 елементів, і між кожною ітерацією Event Loop має можливість обробити інші події, такі як таймери чи мережеві запити.
Спеціальні глобальні змінні ES Modules
У ES Modules (type: "module") деякі традиційні глобальні змінні CommonJS недоступні, але є їхні альтернативи.
import.meta: метадані модуля
Об'єкт import.meta надає інформацію про поточний модуль:
// Файл: /Users/arakviel/projects/app/src/server.js
console.log(import.meta.url);
// file:///Users/arakviel/projects/app/src/server.js
// Отримання __dirname та __filename для ES Modules
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('Module file:', __filename);
console.log('Module directory:', __dirname);
Динамічний імпорт: import()
ES Modules підтримують динамічне завантаження модулів через функцію import(), яка повертає Promise:
async function loadModule(moduleName) {
try {
const module = await import(`./${moduleName}.js`);
return module;
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error.message);
}
}
// Умовне завантаження залежно від конфігурації
const config = { useAdvancedFeatures: true };
if (config.useAdvancedFeatures) {
const advanced = await import('./advanced-features.js');
advanced.initialize();
}
import() працює також у CommonJS модулях (починаючи з Node.js 13), на відміну від статичного import ... from ..., який доступний лише в ES Modules.Порівняльна таблиця: CommonJS vs ES Modules
| Особливість | CommonJS | ES Modules |
|---|---|---|
| Синтаксис імпорту | const fs = require('fs') | import fs from 'node:fs' |
| Синтаксис експорту | module.exports = {...} | export default {...} або export const ... |
__dirname | ✅ Доступно | ❌ Потрібно через import.meta.url |
__filename | ✅ Доступно | ❌ Потрібно через import.meta.url |
require.resolve() | ✅ Доступно | ❌ Немає аналога |
| Динамічний імпорт | require(variable) | import(variable) (async) |
| Top-level await | ❌ Не підтримується | ✅ Підтримується |
| Завантаження | Синхронне | Асинхронне |
| Використання | Традиційний Node.js | Сучасний стандарт |
"type": "module" у package.json. Більшість популярних бібліотек вже підтримують обидва формати через умовний експорт (conditional exports):{
"name": "my-library",
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
Резюме та ключові висновки
✅ Основні глобальні об'єкти
- global / globalThis — глобальний простір імен, доступний скрізь без імпорту.
- process — метадані процесу, змінні оточення, аргументи командного рядка.
- console — логування з підтримкою форматування, таблиць, вимірювання часу.
- Buffer — робота з бінарними даними поза купою V8.
- Таймери —
setTimeout,setImmediate,process.nextTickдля планування асинхронних операцій.
🎯 Практичні рекомендації
- Використовуйте
process.envдля конфігурації замість жорсткого кодування значень. - Завжди обробляйте сигнали
SIGTERMтаSIGINTдля graceful shutdown. - Уникайте забруднення
globalпростору — використовуйте модулі для обміну даними. - Віддавайте перевагу
Promiseтаasync/awaitнадprocess.nextTickдля більшості асинхронного коду. - Використовуйте
Bufferдля ефективної роботи з великими бінарними файлами.
Інтерактивні запитання для самоперевірки
Node.js загортає кожен модуль у функцію-обгортку (wrapper function), створюючи ізольований простір імен. Це означає, що const, let та var на верхньому рівні файлу належать до області видимості модуля, а не глобального об'єкта. Це запобігає конфліктам імен між модулями та є однією з ключових переваг модульної системи CommonJS/ES Modules.
// Node.js автоматично загортає ваш код:
(function(exports, require, module, __filename, __dirname) {
const myVariable = 'module-scoped'; // НЕ стає global.myVariable
// Ваш код тут...
});
process.cwd() повертає каталог, з якого запущено процес Node.js (залежить від поточної директорії в терміналі). __dirname повертає абсолютний шлях до директорії, де знаходиться поточний JavaScript-файл. Якщо ви запускаєте node /home/user/app/src/index.js з директорії /tmp, то process.cwd() поверне /tmp, а __dirname поверне /home/user/app/src. Для читання файлів відносно модуля завжди використовуйте __dirname або import.meta.url.setTimeout(() => console.log('A'), 0);
setImmediate(() => console.log('B'));
Promise.resolve().then(() => console.log('C'));
process.nextTick(() => console.log('D'));
console.log('E');
Відповідь:
E (синхронний код)
D (process.nextTick має найвищий пріоритет)
C (Promise microtasks після nextTick)
A (setTimeout у Timers Phase)
B (setImmediate у Check Phase)
Проте порядок між A та B може варіюватися залежно від контексту виконання (головний модуль vs I/O callback).
Buffer.allocUnsafe() не ініціалізує виділену пам'ять нулями, тому буфер містить залишки попередніх даних з пам'яті процесу. Якщо випадково прочитати такий буфер до його заповнення, можна витягти чужі дані — паролі, ключі, персональну інформацію. Це серйозна вразливість безпеки. Використовуйте allocUnsafe() лише у високопродуктивному коді, де ви гарантовано перезапишете кожен байт перед читанням.// ❌ НЕПРАВИЛЬНО: ігнорування помилок
Promise.reject(new Error('Oops')); // Неперехоплена помилка
// ✅ ПРАВИЛЬНО: глобальний обробник
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Логування у систему моніторингу (Sentry, Datadog)
process.exit(1); // Завершити процес для перезапуску
});
// ✅ КРАЩЕ: завжди обробляти помилки у коді
async function safeOperation() {
try {
await riskyAsyncOperation();
} catch (error) {
console.error('Expected error:', error.message);
// Обробка помилки
}
}
Наступна лекція: Вбудовані модулі — Файлова система (fs)
У наступному матеріалі ми детально розглянемо модуль fs для роботи з файловою системою: читання та запис файлів, робота з директоріями, моніторинг змін, потокова обробка великих файлів та відмінності між синхронними, callback-based та Promise-based API.