Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке Node.js і як він працює?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Node.js** - це середовище виконання JavaScript, побудоване на движку V8 від Chrome, яке запускає JS-код поза браузером. libuv виконує асинхронний I/O через пул потоків, тому єдиний JS-потік не блокується на читанні файлів чи мережевих запитах. ```js const http = require('http'); http.createServer((req, res) => res.end('Hello')).listen(3000); ``` **Ключове:** один JS-потік + неблокуючий I/O = тисячі одночасних з'єднань без додаткових потоків.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Node.js** - це середовище виконання JavaScript, яке запускає JS-код поза браузером, на сервері. ## Теорія ### TL;DR - Node.js - не фреймворк і не мова, а середовище виконання (runtime) для JavaScript - Складається з V8 (JS-движок Chrome) + libuv (C++-бібліотека для асинхронного I/O) - Один JS-потік + неблокуючий I/O: код не чекає на завершення файлових чи мережевих операцій - Підходить для I/O-навантажених задач (API, реальний час, стрімінг); погано справляється з важкими обчисленнями - Екосистема npm: понад 2 мільйони пакетів ### Швидкий приклад ```js const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello from Node.js'); }); server.listen(3000, () => { console.log('Listening on port 3000'); }); ``` HTTP-сервер на 10 рядків. Без Apache, без Nginx. Node.js сам управляє мережею через вбудований модуль `http`. ### Як Node.js обробляє запити JS-потік один. Одна точка виконання в будь-який момент. Але що відбувається, коли 1000 користувачів одночасно звертаються до сервера? Node.js делегує блокуючу роботу - читання файлів, запити до БД, мережеві виклики - бібліотеці libuv. libuv утримує пул потоків (4 за замовчуванням), який виконує реальний I/O на рівні ОС. Коли операція завершена, libuv додає callback у чергу подій. Event loop (цикл подій) забирає його і виконує на JS-потоці. ``` Ваш JS-код | Node.js APIs (fs, http, net, crypto...) | libuv (event loop + thread pool) | ОС (файлова система, мережа, таймери) ``` JS-потік вільний увесь час, поки ОС виконує реальну роботу. Саме тому Node.js обробляє тисячі одночасних з'єднань на одному потоці - здебільшого він просто чекає на I/O, а не обчислює. ### V8 і libuv V8 компілює JavaScript у нативний машинний код. Не інтерпретує, не проганяє через повільний байткод - компілює одразу. Саме тому Node.js швидкий у виконанні JS. libuv - друга половина рівняння. Це C++-бібліотека, написана спеціально для Node.js, але зараз використовується й іншими проектами. Вона забезпечує: - цикл подій (event loop) - асинхронний файловий I/O (пул потоків, 4 за замовчуванням, налаштовується через `UV_THREADPOOL_SIZE`) - TCP/UDP сокети - DNS-резолвінг - таймери (`setTimeout`, `setInterval`) Без libuv Node.js був би просто V8 без можливості асинхронно взаємодіяти з ОС. ### Коли Node.js не справляється Один JS-потік - реальне обмеження. Запусти важку CPU-операцію - парсинг великого JSON, обробку зображень, ML-інференс - і event loop заблокується. Всі інші запити чекатимуть. ```js // Блокує event loop для всіх користувачів, поки виконується app.get('/report', (req, res) => { const result = generateHeavyReport(); // блокує JS-потік res.json({ result }); }); ``` Для CPU-інтенсивних задач використовуй Worker Threads (доступні з Node 12) або винось роботу в окремий сервіс. Node.js добре працює як API-шлюз і координатор, але не як обчислювальний рушій. ### Де Node.js доречний - REST API і GraphQL-сервери - WebSocket-сервери і застосунки реального часу (чат, лайв-оновлення, колаборативні інструменти) - CLI-інструменти і build-скрипти (webpack, Vite, ESLint - все на Node) - Serverless-функції (AWS Lambda, Vercel, Cloudflare Workers) - Мікросервіси, що переважно виконують I/O Екосистема npm - частина цінності. Понад 2 мільйони пакетів означають, що більшість задач вже мають готове рішення. ### Типові помилки **Блокування event loop синхронними операціями** ```js // Погано - блокує весь сервер під час читання const data = fs.readFileSync('./large-file.json'); // Добре - libuv бере на себе, JS-потік залишається вільним fs.readFile('./large-file.json', (err, data) => { // виконається, коли файл буде готовий }); ``` **Припущення, що Node.js багатопотоковий** Node.js має один JS-потік. `setTimeout` і `setInterval` не виконуються паралельно - вони ставлять callback у чергу. Якщо event loop заблокований, таймери спрацьовують із запізненням. **Використання Node.js для CPU-важких задач без Worker Threads** Один CPU-bound запит може затримати відповідь для всіх інших користувачів. Потрібна важка CPU-робота - використовуй `worker_threads` або дочірній процес. **Ігнорування необроблених відмов Promise** Починаючи з Node 15, необроблений rejection завершує процес за замовчуванням. Завжди додавай `.catch()` або `try/catch` в async-функціях. ```js // Завершує процес у Node 15+ fetchUserData(userId); // async-функція, обробки помилок немає ``` ### Де зустрічається в реальних проектах - Express, Fastify, Koa - HTTP-фреймворки на основі вбудованого `http`-модуля Node.js - Next.js - SSR і API routes виконуються в Node.js-процесі - Socket.io - комунікація в реальному часі через WebSocket - NestJS - структурований бекенд-фреймворк для великих застосунків - Інструментарій: Vite, webpack, компілятор TypeScript, ESLint, Prettier Я бачив команди, що обирали Node.js для конвеєра обробки даних, де більшість часу йшла на очікування відповідей від БД. Node.js добре справився з конкурентністю без додаткової інфраструктури. ### Follow-up питання **Q:** Чим Node.js відрізняється від JS-движка браузера? **A:** Обидва можуть використовувати V8, але Node.js додає libuv для доступу до ОС (файлова система, мережа) і не має DOM, `window` чи браузерних API. Браузер має `document`, `fetch`, `localStorage`. Node - `fs`, `net`, `child_process`. **Q:** Чому Node.js однопотоковий, але обробляє багато з'єднань? **A:** JS-виконання однопотокове, але I/O виконує пул потоків libuv і асинхронні виклики ОС. Більшість часу Node.js чекає на I/O, а не обчислює. Поки він чекає, event loop обробляє інші callback-и. **Q:** Що відбувається, коли event loop заблокований? **A:** Усі запити в черзі стоять. Якщо JS-потік виконує 5-секундне обчислення, всі відповіді затримуються на ці 5 секунд. Саме тому Node.js не підходить для CPU-важких задач без Worker Threads. **Q:** Скільки потоків насправді використовує Node.js? **A:** Один JS-потік. libuv додає 4 потоки в пулі за замовчуванням (змінюється через `UV_THREADPOOL_SIZE`). Мережевий I/O асинхронний на рівні ОС і не потребує пулу. Типовий Node-процес показує 6-8 потоків у `top` або `htop`. **Q:** Чи є libuv специфічним для Node.js? **A:** Ні. libuv - незалежна open-source C++-бібліотека (libuv/libuv на GitHub). Node.js був першопричиною її створення, але інші проекти також її використовують. ## Приклади ### Простий HTTP-сервер ```js const http = require('http'); const server = http.createServer((req, res) => { if (req.url === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); return; } res.writeHead(404); res.end('Not found'); }); server.listen(3000, () => console.log('Сервер на порту 3000')); ``` Без залежностей. Модуль `http` бере на себе TCP-з'єднання, парсинг HTTP-запитів і підтримує сервер активним. ### Асинхронне читання файлу з обробкою помилок ```js const fs = require('fs').promises; async function loadConfig(path) { try { const raw = await fs.readFile(path, 'utf8'); return JSON.parse(raw); } catch (err) { console.error('Не вдалось завантажити конфіг:', err.message); return null; } } loadConfig('./config.json').then(config => { console.log('Назва застосунку:', config?.name); }); ``` `fs.promises.readFile` передає роботу в пул потоків libuv. JS-потік залишається вільним до готовності файлу. `await` зупиняє тільки цю async-функцію, але не весь event loop. ### Порядок виконання в event loop ```js console.log('1 - синхронно'); setTimeout(() => console.log('3 - макротаск'), 0); Promise.resolve().then(() => console.log('2 - мікротаск')); console.log('4 - синхронно'); // Виведе: // 1 - синхронно // 4 - синхронно // 2 - мікротаск (Promise callback) // 3 - макротаск (setTimeout) ``` Мікротаски (Promise callback-и) виконуються раніше за макротаски (setTimeout), навіть якщо затримка 0 мс. Це дивує розробників, які очікують, що `setTimeout(..., 0)` спрацює одразу після поточного синхронного блоку.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.