Как реализовать функцию sleep (задержку) в TypeScript?
Правильная реализация sleep в TypeScript через Promise и async/await
Проблема вашего кода в том, что setTimeout — это асинхронная функция, которая не блокирует поток выполнения. Она просто регистрирует колбэк, который выполнится позже, но сам код продолжает выполняться дальше. Чтобы сделать паузу, нужно завернуть setTimeout в Promise и дождаться его разрешения с помощью await.
Вот правильная реализация:
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function loadData() {
console.log('Начинаю загрузку...');
await sleep(2000); // ждём 2 секунды
console.log('Данные загружены'); // выполнится через 2 секунды
}
loadData();
Как это работает?
- Promise — это объект, представляющий асинхронную операцию. Он может находиться в трёх состояниях: ожидание (pending), выполнено (fulfilled) или отклонено (rejected).
- setTimeout(resolve, ms) — через ms миллисекунд вызывает функцию resolve, которая переводит Promise в состояние "выполнено".
- await — приостанавливает выполнение асинхронной функции до тех пор, пока Promise не будет разрешён. При этом поток не блокируется — другие задачи могут выполняться.
Полный пример с обработкой ошибок и типизацией
/**
* Функция задержки с возможностью отмены через AbortSignal
*/
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(new Error('Sleep was cancelled'));
return;
}
const timer = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('Sleep was cancelled'));
}, { once: true });
});
}
// Пример использования с отменой
async function exampleWithCancel() {
const controller = new AbortController();
// Отменяем через 1 секунду
setTimeout(() => controller.abort(), 1000);
try {
await sleep(3000, controller.signal);
console.log('Дождались!');
} catch (error) {
console.log('Сон прерван:', error.message);
}
}
Универсальное решение для Angular
В Angular-приложениях эту функцию можно использовать как сервис или просто как утилиту. Рекомендуется обернуть её в Injectable сервис для лучшей тестируемости:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class SleepService {
sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Использование в компоненте
@Component({...})
export class MyComponent {
constructor(private sleepService: SleepService) {}
async loadData() {
this.loading = true;
await this.sleepService.sleep(2000);
// ... загрузка данных
this.loading = false;
}
}
Важные замечания
- Функция
sleepдолжна возвращатьPromise<void>— это правильная типизация в TypeScript. - Вызывать её нужно только внутри
asyncфункции с использованиемawait. - Не используйте
sleepв синхронном коде — это приведёт к ошибке или неожиданному поведению. - В Node.js с версии 16 появился встроенный
setTimeoutс промисом:import { setTimeout } from 'timers/promises', но решение выше более универсально.
Этот подход работает везде: в браузере, Node.js, Angular, React и любых других средах, где есть Promise и setTimeout.
<<Альтернативный подход: использование rxjs (для Angular-разработчиков)
Если вы работаете с Angular, у вас уже подключён RxJS. Вместо Promise можно использовать Observable с оператором delay или timer. Это особенно удобно, когда нужно комбинировать задержку с другими асинхронными операциями в реактивном стиле.
import { timer } from 'rxjs';
import { firstValueFrom } from 'rxjs';
// Функция sleep через RxJS
function sleepRx(ms: number): Promise<void> {
return firstValueFrom(timer(ms));
}
// Или как Observable (если не хотите конвертировать в Promise)
function sleepObservable(ms: number) {
return timer(ms);
}
// Использование
async function example() {
console.log('Старт');
await sleepRx(2000);
console.log('Прошло 2 секунды');
}
// Или в реактивном стиле
sleepObservable(3000).subscribe(() => {
console.log('Прошло 3 секунды');
});
Когда выбирать этот подход?
- Если ваш проект уже активно использует RxJS (как в Angular).
- Когда нужно отменить задержку — Observable легко отписывается.
- Для комбинирования с другими операторами RxJS (например,
switchMap,mergeMap).
Обратите внимание: timer(ms) генерирует значение 0 после указанной задержки. Если вам нужно просто подождать, используйте firstValueFrom для конвертации в Promise или подпишитесь напрямую.
Недостаток: добавляет зависимость от RxJS, если проект его не использует. Для чистого TypeScript или Node.js лучше применять Promise-решение из первого ответа.