Цикл событий координирует работу
В обычном сценарии JavaScript обработчиков выполняется в потоке цикла событий. Ввод-вывод может обслуживаться неблокирующими механизмами системы, а некоторые операции используют рабочий пул. Когда результат готов, соответствующее продолжение получает возможность выполняться. Это позволяет не держать обработчик занятым всё время ожидания внешнего ответа. Но если внутри обработчика запущен длительный синхронный цикл, он продолжает занимать этот поток. Слово async перед функцией не переносит её вычисления автоматически в другой поток. Аналогично Promise описывает получение результата, а не гарантирует отдельное вычислительное ядро. Поэтому полезно читать не только форму вызова, но и то, какую работу фактически выполняет используемый API.
Ожидание и вычисление требуют разных решений
Запрос к базе данных и преобразование огромного массива могут занимать одинаковое время по часам, но нагружают систему по-разному. В первом случае значительная часть времени может проходить в ожидании. Во втором приложение само расходует процессор на обработку. Для ресурсоёмкого JavaScript существуют worker threads, но их применение имеет стоимость передачи данных и управления задачами. Не стоит создавать неограниченное число рабочих потоков на каждый запрос. Нужны ограничения очереди, понятная обработка ошибок и способ не принимать больше работы, чем система выдерживает. Иногда полезнее изменить алгоритм, уменьшить вход или обрабатывать данные частями. Выбор делают после измерения, а не по общему правилу всё вынести в фон.
Надёжный обработчик ограничивает неизвестное
Для каждого внешнего запроса определите допустимое время ожидания, возможные ошибки и реакцию на отмену клиентом. Входные данные проверяют до дорогой обработки, включая размер и структуру. Даже корректный по формату объект может оказаться слишком большим для безопасной обработки в одном шаге. Наблюдайте не только среднее время ответа, но и задержки отдельных запросов при одновременной нагрузке. Быстрая одиночная проверка не показывает, как тяжёлая операция влияет на соседние лёгкие запросы. Логи должны помогать связать событие с запросом, не раскрывая секреты и лишние персональные данные. Разделяйте ошибку приложения, недоступность зависимости и превышение установленного лимита: это разные причины и разные действия.
Попробуйте на практике
Постройте временную модель двух запросов на бумаге. Запуск сервера и установка пакетов не нужны.
- Нарисуйте запрос A, который ожидает внешний ответ, и запрос B с короткой обработкой. Покажите, где цикл событий может переключиться к доступной работе.
- Замените ожидание A длинным синхронным вычислением. Отметьте, почему обработка B теперь задерживается.
- Предложите два решения: уменьшить вычислительную задачу или передать её ограниченному пулу worker threads. Запишите цену каждого решения.
- Добавьте для обоих запросов ограничение входного размера, условие завершения и обработку ошибки. Укажите показатель для сравнения вариантов.
Как проверить результат. Модель различает ожидание и занятой процессор. Async не изображён как автоматический запуск в другом потоке, а вариант с рабочим пулом имеет предел числа задач.
Частые вопросы
Node.js использует только один поток вообще?
Нет. Один поток исполнения JavaScript в обычном цикле событий не означает отсутствие других потоков в среде. Важны конкретная операция и выбранный механизм выполнения.
Promise.all всегда ускоряет работу?
Нет. Он координирует несколько обещаний, но не делает синхронные вычисления параллельными. Кроме того, одновременный запуск слишком многих внешних операций может перегрузить зависимость.
Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.