readFileSync і його небезпека
1. Що робить readFileSync
import fs from 'fs';
const data = fs.readFileSync('./data.txt', 'utf8');
console.log(data);Метод fs.readFileSync() читає файл синхронно - тобто блокує виконання програми, поки операція повністю не завершиться.
Node.js не перейде до наступного рядка коду, поки файл не буде прочитано.
2. Node.js - однопоточний
Ось головна причина, чому синхронний I/O небезпечний:
Node.js працює на одному потоці (event loop). Поки одна операція виконується синхронно, весь потік блокується.
Це означає:
- поки
readFileSync()читає файл, - жодні інші запити чи події не обробляються: ні HTTP-запити, ні таймери, ні обробники подій.
3. Що реально відбувається при readFileSync()
Асинхронна версія (fs.readFile)
Event Loop:
→ Відправив задачу "прочитати файл" у потік (thread pool)
→ Звільнився і може обслуговувати інші запити
→ Коли файл готовий - викликає callbackСинхронна версія (fs.readFileSync)
Event Loop:
→ Почав читати файл
⏸ Зупинився, чекає результату
Більше нічого не робить
→ Продовжує лише після завершення операції4. Приклад проблеми
server.js
import http from 'http';
import fs from 'fs';
const server = http.createServer((req, res) => {
const data = fs.readFileSync('./bigfile.txt', 'utf8');
res.end(data);
});
server.listen(3000);Що станеться:
- Перший запит читає файл 2 секунди - усе інше зупиняється;
- Інші запити чекають;
- Сервер "зависає" під навантаженням.
Правильний варіант (асинхронно)
import http from 'http';
import { readFile } from 'fs/promises';
const server = http.createServer(async (req, res) => {
const data = await readFile('./bigfile.txt', 'utf8');
res.end(data);
});
server.listen(3000);Тепер Node може:
- одночасно читати файл,
- і обслуговувати інші запити в черзі подій.
5. Приклад "небезпечної" ситуації
for (let i = 0; i < 1000; i++) {
fs.readFileSync('./data.txt', 'utf8');
}Тут кожна операція чекає попередню. Якщо файл великий - застосунок "зависне" на секунди чи хвилини.
Тоді як асинхронна версія:
Promise.all(
Array(1000).fill().map(() => readFile('./data.txt', 'utf8'))
);читає файли паралельно (у thread pool).
6. Продуктивність у цифрах
| Метод | Час (приблизно) | Потік блокується |
|---|---|---|
readFileSync() | 100-300 мс на файл | Так |
readFile() | 100-300 мс (у фоні) | Ні |
readFile (через fs/promises) | 100-300 мс (у фоні) | Ні |
На сервері з 1000 запитів на секунду
кожен readFileSync() може "заморозити" потік і перетворити Node.js із високопродуктивного на однопотоковий bottleneck.
7. Коли readFileSync() допустимий
Можна використовувати:
- У скриптах, CLI-утилітах, міграціях (де немає конкуренції запитів);
- Під час старту застосунку для завантаження конфігурації;
- У тестах, де потрібно просто отримати дані;
- Коли продуктивність не критична.
Не можна використовувати:
- В HTTP-серверах, Express/Fastify маршрутах;
- В API чи обробниках подій;
- В асинхронних чергах;
- У продакшені з одночасними користувачами.
8. Аналогія
Уяви, що Node.js - це офіціант:
readFile()- він передає замовлення на кухню й обслуговує інших клієнтів;readFileSync()- він стоїть на місці й чекає, поки кухар приготує страву.
9. Як правильно замінити readFileSync()
| Було (погано) | Стало (добре) |
|---|---|
fs.readFileSync() | await fs.promises.readFile() |
fs.writeFileSync() | await fs.promises.writeFile() |
fs.existsSync() | await fs.promises.access() |
Коротке резюме
| Пункт | readFileSync() | readFile() / fs.promises.readFile() |
|---|---|---|
| Виконання | Синхронне | Асинхронне |
| Блокує потік | Так | Ні |
| Підходить для сервера | Ні | Так |
| Простота | Так | Так (через await) |
| Продуктивність | Погана під навантаженням | Відмінна |
Одним реченням
fs.readFileSync()небезпечний у серверних застосунках, тому що блокує весь event loop, заважаючи Node.js обробляти інші запити, і перетворює асинхронний сервер на "завислий" однопоточний процес.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.