Платформа Node.js як середовище виконання

Глобальні об\

global, process, console, __dirname, __filename, Buffer, setTimeout/setInterval

Глобальні об'єкти 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
У сучасному коді (2026) рекомендується використовувати 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. Перші два елементи завжди фіксовані:

  1. process.argv[0] — повний шлях до виконуваного файлу Node.js (наприклад, /usr/local/bin/node)
  2. process.argv[1] — повний шлях до запущеного JavaScript-файлу
  3. 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);
}

Приклад використання цього скрипту:

node temperature.js 25 celsius
$ node temperature.js 25 celsius
25°C = 77.00°F = 298.15K
$ node temperature.js 100 fahrenheit
100°F = 37.78°C = 310.93K
$ node temperature.js
Usage: node temperature.js <value> <unit>
Example: node temperature.js 25 celsius
Для складних CLI-застосунків використовуйте спеціалізовані бібліотеки парсингу аргументів, такі як 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);
}

Змінні оточення передаються при запуску процесу:

NODE_ENV=production PORT=8080 node server.js
$ NODE_ENV=production PORT=8080 node server.js
Starting server in production mode on port 8080

Для зручної роботи зі змінними оточення у розробці часто використовують файл .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

Ключова відмінність стає очевидною при запуску скрипту з різних каталогів:

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

rectangle "Файлова система" {
  folder "/Users/arakviel" as home #E2E8F0 {
    folder "projects" as projects #DBEAFE {
      folder "my-app" as app #DCFCE7 {
        folder "src" as src #FEF3C7 {
          file "index.js" as index #FFFFFF
        }
      }
    }
  }
}

note right of home
  Користувач запускає команду:
  $ cd /Users/arakviel
  $ node projects/my-app/src/index.js
  
  process.cwd() → /Users/arakviel
  __dirname → /Users/arakviel/projects/my-app/src
end note

@enduml

Практичне застосування — читання конфігураційного файлу:

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');
У ES Modules (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);
node users.js
┌─────────┬────┬───────────┬───────────────────────┬────────────┐
│ (index) │ id │ name │ email │ role │
├─────────┼────┼───────────┼───────────────────────┼────────────┤
│ 0 │ 1 │ 'Alice' │ 'alice@example.com' │ 'admin' │
│ 1 │ 2 │ 'Bob' │ 'bob@example.com' │ 'user' │
│ 2 │ 3 │ 'Charlie' │ 'charlie@example.com' │ 'moderator'│
└─────────┴────┴───────────┴───────────────────────┴────────────┘

Можна вибірково показати лише певні колонки:

console.table(users, ['name', 'role']);
node users.js
┌─────────┬───────────┬────────────┐
│ (index) │ name │ role │
├─────────┼───────────┼────────────┤
│ 0 │ 'Alice' │ 'admin' │
│ 1 │ 'Bob' │ 'user' │
│ 2 │ 'Charlie' │ 'moderator'│
└─────────┴───────────┴────────────┘

Вимірювання часу виконання: 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');
node performance.js
Step 1: Validation: 5.234ms
Step 2: Processing: 42.156ms
Total Operation: 47.891ms

Для проміжних вимірювань без зупинки таймера використовуйте 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');
node config.js
Application started
Database Configuration
Host: localhost
Port: 5432
Database: myapp_production
API Configuration
Base URL: https://api.example.com
Timeout: 5000
Rate Limiting
Max requests per minute: 100
Burst limit: 20
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);
node trace-example.js
Trace: Invalid order ID detected
at validateOrder (/app/trace-example.js:6:13)
at processOrder (/app/trace-example.js:2:3)
at Object.<anonymous> (/app/trace-example.js:10:1)
at Module._compile (node:internal/modules/cjs/loader:1358:14)
У продакшн-середовищі замість вбудованого 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
Порядок байтів (Byte Order / Endianness):
  • Little-Endian (LE): молодший байт зберігається першим. Використовується у процесорах x86, x86-64, ARM.
  • Big-Endian (BE): старший байт зберігається першим. Використовується у мережевих протоколах (Network Byte Order).
Node.js надає обидва варіанти методів: 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
У сучасному Node.js (версія 18+) також доступні Typed Arrays та ArrayBuffer зі стандарту ECMAScript для роботи з бінарними даними. Вони працюють як у Node.js, так і у браузері, що робить код переносимим (portable). Buffer залишається зручним для серверних задач завдяки додатковим методам, таким як toString() з підтримкою різних кодувань.

Візуалізація структури Buffer у пам'яті

Buffer Memory Layout
Hex Dump / ASCII
0x00401000
48656C6C6F2C20576F726C6421000000
Offset: 13 bytes
Big Endian

У цій візуалізації показано буфер з рядком "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);
node timers.js
Interval tick 1
Interval tick 2
Interval tick 3
Interval tick 4
Interval tick 5
Interval stopped
Точність таймерів: Node.js не гарантує точного виконання через вказану кількість мілісекунд. Якщо Event Loop зайнятий обробкою тривалого синхронного коду або інших callbacks, таймер спрацює лише після завершення поточної фази. Для високоточних вимірювань часу використовуйте 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');

Очікуваний порядок виконання:

node immediate.js
1: Script start
2: Script end
3: setTimeout callback
4: setImmediate callback

Проте порядок між 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');
  });
});
node immediate-io.js
Inside I/O callback
setImmediate inside I/O
setTimeout 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');
node nexttick.js
1: Start
1.5: Synchronous end
2: nextTick
3: Promise
4: setTimeout

Порядок пріоритетів у Event Loop:

Loading diagram...
graph TD
    A[Синхронний код] --> B[process.nextTick callbacks]
    B --> C[Promise microtasks]
    C --> D[setTimeout/setInterval]
    D --> E[setImmediate]
    
    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#f59e0b,stroke:#b45309,color:#ffffff
    style C fill:#10b981,stroke:#047857,color:#ffffff
    style D fill:#64748b,stroke:#334155,color:#ffffff
    style E fill:#8b5cf6,stroke:#6d28d9,color:#ffffff
Небезпека нескінченного nextTick: якщо callback, зареєстрований через process.nextTick(), рекурсивно викликає сам себе, Event Loop ніколи не перейде до наступної фази. Це призведе до блокування всіх інших операцій, включаючи таймери, мережеві події та завершення процесу:
// ❌ АНТИПАТЕРН: блокування Event Loop
function recursiveNextTick() {
  process.nextTick(recursiveNextTick);
}
recursiveNextTick();

// Process залипне, таймери не спрацюють
setTimeout(() => {
  console.log('This will never execute');
}, 1000);
Для запобігання цьому Node.js встановлює ліміт на кількість 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 з таймерами

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

:Синхронний код виконано;

repeat
  :nextTick Queue;
  note right: process.nextTick callbacks
  
  :Microtasks (Promises);
  note right: Promise.then/catch/finally
  
  :Timers Phase;
  note right: setTimeout, setInterval
  
  :I/O Callbacks Phase;
  note right: Мережеві події, файли
  
  :Idle / Prepare;
  note right: Внутрішні операції Node.js
  
  :Poll Phase;
  note right: Очікування нових I/O подій
  
  :Check Phase;
  note right: setImmediate callbacks
  
  :Close Callbacks;
  note right: socket.on('close', ...)

repeat while (Event Loop активний?)

:Процес завершено;

@enduml

Практичний приклад: розбиття тривалих обчислень

Одна з типових задач — обробка великого масиву даних без блокування 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);
node batch-processing.js
Processing started...
This timer executes during processing
Processing complete!
First element: 0
Last element: 1999998

Завдяки 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

ОсобливістьCommonJSES 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Сучасний стандарт
Міграція на ES Modules у 2026:ES Modules є офіційним стандартом ECMAScript і підтримуються як у Node.js, так і у браузерах. Для нових проєктів рекомендується використовувати ES Modules із додаванням "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 для ефективної роботи з великими бінарними файлами.

Інтерактивні запитання для самоперевірки


Наступна лекція: Вбудовані модулі — Файлова система (fs)

У наступному матеріалі ми детально розглянемо модуль fs для роботи з файловою системою: читання та запис файлів, робота з директоріями, моніторинг змін, потокова обробка великих файлів та відмінності між синхронними, callback-based та Promise-based API.

Copyright © 2026