JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Когда использовать sync/async методы, best practices

Когда использовать sync/async методы, best practices

Модуль 4: Node.js, Next.js и Angular
3 уровень , 4 лекция
Открыта

1. Введение

Когда вы работаете с файловой системой в Node.js, почти каждый метод (например, fs.readFile, fs.writeFile, fs.unlink и т.д.) существует в двух вариантах:

  • Асинхронный: работает с колбэком или возвращает Promise (например, fs.readFile, fs.promises.readFile).
  • Синхронный: блокирует выполнение кода до завершения операции (например, fs.readFileSync).

Чем они отличаются?

  • Асинхронные методы — не блокируют основной поток выполнения (event loop). Вы "запускаете" операцию, а Node.js продолжает выполнять остальной код, пока ждёт завершения работы с файлом.
  • Синхронные методы — полностью останавливают выполнение кода, пока операция не завершится. Пока файл не прочитан или не записан, Node.js не делает ничего другого.

Маленькая аналогия

Представьте, что вы — официант в ресторане.
- Асинхронный метод — вы сделали заказ на кухню и пошли обслуживать других клиентов, пока блюдо готовится.
- Синхронный метод — вы стоите и ждёте, пока повар приготовит блюдо, и все остальные клиенты томятся в ожидании.

2. Демонстрация: как работает блокировка event loop

Давайте посмотрим на простой пример.

const fs = require('fs');

console.log('Перед чтением файла');

const data = fs.readFileSync('bigfile.txt', 'utf-8');
console.log('Файл прочитан, размер:', data.length);

console.log('После чтения файла');

Если файл 'bigfile.txt' очень большой, то строка 'После чтения файла' появится в консоли только после того, как файл полностью прочитан. Всё остальное в вашем приложении (включая обработку запросов пользователей) будет ждать.

Теперь асинхронная версия:

const fs = require('fs');

console.log('Перед чтением файла');

fs.readFile('bigfile.txt', 'utf-8', (err, data) => {
    if (err) throw err;
    console.log('Файл прочитан, размер:', data.length);
});

console.log('После чтения файла');

Здесь Node.js сразу же выведет 'После чтения файла', не дожидаясь окончания чтения файла. Всё остальное приложение продолжит работать.

3. Когда использовать синхронные методы

Синхронные методы (fs.readFileSync, fs.writeFileSync и др.) стоит использовать только в следующих случаях:

  • Скрипты для разовой обработки данных
    Например, миграции, генерация файлов, парсеры, которые запускаются вручную и не обслуживают пользователей в реальном времени.
  • Во время инициализации приложения
    Например, при старте сервера нужно один раз прочитать конфиг или шаблон, чтобы потом использовать его в памяти. В этот момент сервер ещё не начал принимать запросы, и блокировка event loop не страшна.
  • В тестах
    Когда вам нужно быстро и просто прочитать или записать файл между тестами, и асинхронность только усложнит код.
  • В CLI-утилитах
    Если ваша программа — это короткоживущий процесс, который просто что-то читает или пишет и тут же завершается.

Пример: синхронная загрузка конфига при старте

const fs = require('fs');
const config = JSON.parse(fs.readFileSync('config.json', 'utf-8'));
// Далее используем config в приложении

Здесь блокировка не страшна, потому что сервер всё равно не начнёт работу, пока не загрузит конфиг.

4. Когда использовать асинхронные методы

Асинхронные методы (fs.readFile, fs.promises.readFile и др.) — ваш выбор в большинстве случаев, особенно если:

  • Вы пишете сервер (Express, Koa, HTTP)
    Сервер должен обрабатывать множество запросов одновременно. Если вы используете синхронные методы, каждый запрос будет ждать завершения операций предыдущих пользователей.
  • Ваша программа должна быть отзывчивой
    Например, чат, веб-приложение, API, которые не должны "подвисать" из-за долгой операции с файлом.
  • Вы работаете с большими файлами
    Чтение и запись больших файлов может занять секунды, и блокировка event loop в это время недопустима.
  • Любая ситуация, где параллелизм и масштабируемость важнее простоты кода
    Асинхронность позволяет Node.js обслуживать тысячи соединений, пока идёт работа с диском.

Пример: асинхронная обработка запроса

const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
    fs.readFile('index.html', (err, data) => {
        if (err) {
            res.writeHead(500);
            res.end('Ошибка сервера');
            return;
        }
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.end(data);
    });
}).listen(3000);

Здесь каждый запрос к серверу обрабатывается независимо, и чтение файла не блокирует других пользователей.

5. Какой вред могут нанести sync-методы на сервере

Представьте себе популярный сайт, на который одновременно заходят 1000 человек. Каждый раз при открытии страницы сервер читает файл синхронно:

// ОПАСНО!
http.createServer((req, res) => {
    const data = fs.readFileSync('index.html');
    res.end(data);
});

Пока сервер читает файл для одного пользователя, все остальные ждут. Если файл большой или диск медленный, сайт превращается в очередь за булками: "Следующий! А теперь следующий...". В результате:

  • Сервер не масштабируется.
  • Возникают задержки.
  • Пользователи недовольны.

В асинхронной версии сервер может обслуживать много запросов одновременно, потому что чтение файла не блокирует event loop.

6. Асинхронность: callbacks, promises, async/await

Асинхронные методы поддерживают разные подходы:

  • Callbacks (старый стиль, но всё ещё используется)
  • Promises (fs.promises)
  • async/await (самый современный и удобный)

Пример с async/await

const fs = require('fs/promises');

async function handleRequest(req, res) {
    try {
        const data = await fs.readFile('index.html');
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.end(data);
    } catch (err) {
        res.writeHead(500);
        res.end('Ошибка сервера');
    }
}

Асинхронный код с async/await читается почти как синхронный, но не блокирует event loop.

7. Полезные нюансы

Когда можно нарушить правило?

Да, бывают ситуации, когда использовать sync-методы — это не преступление против человечности. Если вы пишете маленькую утилиту, которая просто читает файл и тут же завершается, или если инициализируете данные до старта сервера — смело используйте sync-методы. Главное — никогда не используйте их в обработчиках HTTP-запросов, WebSocket, или любых других местах, где ваш код должен быть отзывчивым и многопоточным (ну, многопоточным в кавычках — в Node.js всё крутится в одном потоке, а асинхронность реализуется через event loop).

Таблица: когда использовать sync/async-методы

Сценарий Рекомендуемый метод Почему
CLI-утилита, скрипт, миграция Sync Простота, неважна блокировка
Инициализация данных при старте Sync Сервер ещё не обслуживает пользователей
Веб-сервер, обработка HTTP-запросов Async Масштабируемость, отзывчивость
Обработка большого количества файлов Async Параллелизм, отсутствие блокировок
Тесты (unit/integration) Sync (обычно) Простота, скорость написания
Внутри циклов, вызываемых пользователем Async Не блокировать event loop

best practices

  • Всегда используйте асинхронные методы в серверном и долгоживущем приложении.
  • Используйте sync-методы только в утилитах, тестах и инициализации.
  • Не смешивайте синхронные и асинхронные методы в одном потоке логики.
  • Обрабатывайте ошибки в асинхронных методах.
  • Не бойтесь асинхронности — с async/await писать такой код почти так же просто, как и синхронный.
  • Если сомневаетесь — выберите асинхронный подход.

8. Практика: сравниваем скорость

Давайте проведём мини-эксперимент. Создайте файл 'bigfile.txt' размером несколько мегабайт. Теперь напишем два скрипта:

Синхронный вариант

const fs = require('fs');

console.time('sync');
for (let i = 0; i < 10; i++) {
    const data = fs.readFileSync('bigfile.txt');
}
console.timeEnd('sync');

Асинхронный вариант

const fs = require('fs/promises');

console.time('async');
Promise.all(
    Array.from({length: 10}).map(() => fs.readFile('bigfile.txt'))
).then(() => {
    console.timeEnd('async');
});

Запустите оба скрипта и сравните время. Асинхронный вариант почти всегда будет быстрее, потому что операции идут параллельно, а не по очереди.

9. Типичные ошибки при работе с sync/async-методами

Ошибка №1: Использование sync-методов в серверном коде.
Это самая распространённая ошибка новичков. Даже если кажется, что "файл маленький, всё быстро", однажды вы выстрелите себе в ногу, когда нагрузка вырастет или файл внезапно станет больше.

Ошибка №2: Забыли обработать ошибку в асинхронном методе.
Асинхронные методы всегда требуют обработки ошибок (через колбэк, .catch() или try/catch в async-функции). Если этого не делать, приложение может "молча" падать или вести себя непредсказуемо.

Ошибка №3: Смешивание sync и async-методов.
Если вы начали писать сервер асинхронно, не вставляйте внезапно sync-методы в середину цепочки — это может привести к неожиданным блокировкам.

Ошибка №4: Использование sync-методов в больших циклах.
Если вам нужно обработать много файлов, не делайте это синхронно в цикле — ваш сервер "зависнет" на всё время обработки.

Ошибка №5: Ожидание "простоты" от sync-методов.
Некоторые новички выбирают sync-методы, потому что "так проще". Но простота обманчива: потом приходится переписывать код, когда приложение начинает тормозить.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 3 уровень, 4 лекция
Недоступна
Синхронное удаление файла с обработкой исключения
Синхронное удаление файла с обработкой исключения
1
Задача
Модуль 4: Node.js, Next.js и Angular, 3 уровень, 4 лекция
Недоступна
Практика — время выполнения sync и async чтения
Практика — время выполнения sync и async чтения
3
Опрос
Работа с файловой системой, 3 уровень, 4 лекция
Недоступен
Работа с файловой системой
Работа с файловой системой
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ