Avatar ·

Как реализовать функцию sleep (задержку) в TypeScript?

Всем привет! Я начинающий разработчик на TypeScript и Angular. Столкнулся с такой задачей: нужно сделать паузу в выполнении кода, например, чтобы имитировать загрузку данных или сделать задержку перед повторным запросом к API. В JavaScript я видел функцию setTimeout, но она не блокирует выполнение, а просто откладывает вызов колбэка. Мне нужно что-то вроде sleep(2000), чтобы выполнение кода остановилось на 2 секунды, а потом продолжилось дальше. Вот что я пробовал: ```typescript function sleep(ms: number) { setTimeout(() => { // что-то должно произойти? }, ms); } async function loadData() { console.log('Начинаю загрузку...'); sleep(2000); // хочу подождать 2 секунды console.log('Данные загружены'); // это выполняется сразу } ``` Но код не работает — второй console.log выполняется сразу, без задержки. Как правильно реализовать sleep в TypeScript? Нужно ли использовать Promise или async/await? Буду благодарен за примеры с пояснениями. Желательно решение, которое будет работать и в браузере, и в Node.js, и в Angular-приложениях. <<>>

Правильная реализация 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-решение из первого ответа.