Проверяет понимание однопоточной модели Node.js и event loop, а также умение выбирать инструменты для CPU-интенсивных задач.
Node.js построен вокруг однопоточного event loop. Весь пользовательский JavaScript выполняется в одном главном потоке, а асинхронность достигается за счёт неблокирующего ввода-вывода: операции с сетью, файлами и таймерами делегируются ядру ОС или пулу потоков libuv, а главный поток лишь получает колбэки. Это отлично работает для I/O-bound нагрузок, но ломается на CPU-bound задачах.
Event loop — это цикл, который последовательно забирает задачи из очередей и выполняет их. Пока одна задача выполняется, цикл не может перейти к следующей. Если функция считает что-то 2 секунды, то на эти 2 секунды сервер перестаёт обрабатывать любые другие запросы: они просто стоят в очереди.
const http = require('http');
function heavy(n) {
let sum = 0;
for (let i = 0; i < n; i++) sum += Math.sqrt(i);
return sum;
}
http.createServer((req, res) => {
if (req.url === '/heavy') {
const r = heavy(1e9); // блокирует event loop на секунды
res.end(String(r));
} else {
res.end('ok'); // этот ответ тоже будет ждать
}
}).listen(3000);
Пока выполняется /heavy, запрос к / не получит ответа, хотя он тривиален.
const { Worker } = require('worker_threads');
function runHeavy(n) {
return new Promise((resolve, reject) => {
const w = new Worker('./heavy.js', { workerData: n });
w.on('message', resolve);
w.on('error', reject);
});
}
// главный поток остаётся свободным
app.get('/heavy', async (req, res) => {
res.end(String(await runHeavy(1e9)));
});
Итог: Node.js стоит применять для I/O-bound задач — API, стриминга, realtime. Для тяжёлых вычислений используйте worker_threads, отдельные сервисы или языки, рассчитанные на многопоточность.