Как сделать паузу в Node.js: setTimeout, async/await и sleep-promise

В отличие от многих языков программирования, JavaScript не предоставляет встроенной функции sleep. Нельзя просто вызвать sleep() и приостановить выполнение. Тем не менее, есть несколько надёжных способов заставить программу Node.js ждать заданное время. В этой статье мы разберём три подхода: setTimeout, async/await и пакет sleep-promise, а также добавим рекомендации, матрицу выбора и проверочные списки.
1. Использование setTimeout() для ожидания
Функция setTimeout() планирует выполнение кода через указанный интервал (в миллисекундах). Она принимает функцию и задержку в миллисекундах. Синтаксис:
setTimeout(function(), timeInMs)Пример: функция, которая печатает сообщение в консоль:
function printSentence() {
console.log("Используем setTimeout()")
}
setTimeout(printSentence, 2000)setTimeout гарантирует, что обработчик будет добавлен в очередь исполнения не раньше, чем через указанный интервал. Но это не гарантия точного срабатывания в момент времени — задержка может быть больше, если поток занят.
Точность setTimeout()
setTimeout() асинхронен и неблокирующий. Отложенный код попадает в цикл событий (event loop) и выполнится, когда стек вызовов будет пустой. Если в это время выполняется тяжёлая синхронная операция, выполнение отложенного кода задержится.
Пример программы:
console.log("Hello World!")
function printSentence() {
console.log("Используем setTimeout()")
}
setTimeout(printSentence, 2000)
console.log("Done")Вывод в консоли:
Hello World!
Done
Используем setTimeout()Этот способ откладывает только функцию, переданную в setTimeout. Остальной код выполняется без паузы.
Блокирующий вариант (не рекомендуется в большинстве случаев)
Если вам нужно принудительно блокировать поток (например, в однопоточном тестовом окружении), можно реализовать цикл ожидания. Это сильно нагружает CPU и блокирует весь процесс:
function delay(ms) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < ms);
}
console.log("Hello World");
delay(2000);
console.log("Будет выведено через 2 секунды!");Минусы: блокировка потока, невозможность обрабатывать другие события, плохая масштабируемость.
2. Использование async/await — рекомендованный подход
await приостанавливает выполнение текущей async-функции до тех пор, пока промис не завершится. Это позволяет писать асинхронный код как синхронный, при этом не блокируя поток выполнения.
Пример реализации delay через Promise:
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function printSentence() {
console.log("Hello World")
await delay(2000);
console.log("Будет выведено через 2 секунды");
}
printSentence();Вывод:
Hello World
Будет выведено через 2 секундыПреимущества: читаемость, неблокирующее поведение, совместимость с современным кодом.
3. Пакет sleep-promise
sleep-promise — маленькая библиотека, которая возвращает промис и позволяет писать await sleep(ms). Установка:
npm install sleep-promiseПример использования:
const sleep = require('sleep-promise');
(async () => {
console.log("Hello World.");
await sleep(2000);
console.log("Будет выведено через две секунды.");
})();Преимущества: готовое решение, лаконичный синтаксис. Минусы: зависимость от внешнего пакета ради короткой функции delay.
Матрица выбора: когда использовать какой метод
| Цель | Рекомендация | Блокирует поток? | Простота |
|---|---|---|---|
| Откладывать одну функцию, не блокируя остальной код | setTimeout() | Нет | Высокая |
| Писать читаемый последовательный асинхронный код | async/await + Promise | Нет | Очень высокая |
| Быстрое решение с готовой функцией | sleep-promise | Нет | Очень высокая |
| Принудительно приостановить весь процесс (редко) | Busy-wait (do…while) | Да, полностью | Низкая |
Ментальные модели и эвристики
- Event loop: представьте очередь задач. setTimeout ставит задачу в очередь, но выполнение зависит от текущей загрузки.
- Блокирование против неблокирования: всё, что использует цикл ожидания (busy-wait), забирает CPU и мешает обработке I/O.
- Promise = обещание: может быть выполнено (resolve) или отклонено (reject). await ждёт завершения promise.
Контрпримеры / когда методы не подходят
- setTimeout: не подходит, если нужно остановить выполнение всей функции/скрипта синхронно. Он только откладывает выполнение куска кода.
- Busy-wait: не подходит в серверных приложениях, где важна отзывчивость и пропускная способность.
- sleep-promise: не нужен, если вы предпочитаете минимальные зависимости; в простом проекте достаточно своей функции delay.
Мини-методология: как выбрать в проекте
- Определите требование: локальная задержка или приостановка всей логики.
- Предпочитайте async/await + Promise для читаемости и тестируемости.
- Используйте setTimeout для запуска отложенных задач без приостановки текущего потока.
- Избегайте busy-wait в продуктивном серверном коде.
- Для утилит и скриптов, где удобство важнее количества зависимостей, рассмотрите sleep-promise.
Сниппет‑шпаргалка (cheat sheet)
- Простая функция delay:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}- Использование внутри async-функции:
await delay(1000); // ждем 1 секунду- Однострочное использование sleep-promise:
const sleep = require('sleep-promise');
await sleep(500);Ролевые контрольные списки
Разработчик:
- Выбрать async/await для последовательного кода
- Отключать busy-wait перед пушем в прод
- Покрыть тестами граничные случаи задержек
DevOps / SRE:
- Проверить, что долгие задержки не влияют на health checks
- Убедиться, что задержки не блокируют пул воркеров
QA:
- Протестировать таймауты при высокой нагрузке
- Проверить тайминги на CI, где время выполнения может быть другим
Факты и ориентиры (fact box)
- Единицы: задержки в JavaScript указываются в миллисекундах (ms).
- Границы: setTimeout гарантирует минимальную задержку, но не точное время срабатывания.
- Рекомендация: для большинства сценариев — async/await + Promise.
Важно: никогда не используйте цикл ожидания (busy-wait) в серверных приложениях: он блокирует event loop и ухудшает масштабируемость.
Примеры отказов и экзотические кейсы
- Среда с низким приоритетом процесса (например, контейнер под высокой нагрузкой) может увеличить реальную задержку setTimeout.
- В некоторых тестовых окружениях (mock time) таймеры могут быть замоканы — учитывайте это при написании тестов.
- В worker threads или child processes поведение идентично, но busy-wait будет блокировать только тот поток/процесс, где выполняется.
Краткое резюме
- setTimeout — идеально для неблокирующих отложенных вызовов.
- async/await + Promise — лучший выбор для последовательного, чистого и неблокирующего кода.
- sleep-promise — удобная обёртка, если вы не против дополнительной зависимости.
- Избегайте блокирующих циклов в продакшн-коде.
Критерии приёмки:
- Задержка реализована без блокирования event loop (если это требование).
- Код покрыт тестами на тайминги и поведение при высокой нагрузке.
- Внешние зависимости минимальны и обоснованы.
1‑строчный глоссарий:
- Promise — объект, представляющий результат асинхронной операции.
- async — ключевое слово, которое делает функцию возвращающей Promise.
- await — оператор, ожидающий завершения Promise внутри async-функции.
- event loop — механизм, управляющий очередью задач и асинхронным выполнением.
- блокирующий код — код, мешающий выполнению других задач в том же потоке.
Если нужно, могу подготовить диаграмму выбора (Mermaid) или готовые тест-кейсы для CI по таймингам.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone