Skip to content
📚 4 min read

Custom setTimeout Examples ​

Learn how to create and use custom setTimeout implementations with advanced features.

Basic Usage ​

typescript
// Promise-based setTimeout
function setTimeoutAsync(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// Cancellable setTimeout
function setTimeoutCancellable(callback: () => void, ms: number): () => void {
  const timeoutId = setTimeout(callback, ms);
  return () => clearTimeout(timeoutId);
}

// Async setTimeout with cleanup
async function withTimeout<T>(
  operation: () => Promise<T>,
  ms: number
): Promise<T> {
  let timeoutId: NodeJS.Timeout;

  const timeoutPromise = new Promise<never>((_, reject) => {
    timeoutId = setTimeout(() => {
      reject(new Error('Operation timed out'));
    }, ms);
  });

  try {
    return await Promise.race([operation(), timeoutPromise]);
  } finally {
    clearTimeout(timeoutId!);
  }
}

Advanced Patterns ​

Recursive setTimeout ​

typescript
class RecursiveTimeout {
  private timeoutId: NodeJS.Timeout | null = null;
  private startTime: number = 0;
  private remainingTime: number = 0;
  private isPaused: boolean = false;

  constructor(
    private callback: () => void,
    private delay: number,
    private maxExecutions: number = Infinity
  ) {}

  start(): void {
    if (this.timeoutId) return;

    this.startTime = Date.now();
    this.remainingTime = this.delay;
    this.scheduleNext();
  }

  private scheduleNext(): void {
    if (this.maxExecutions <= 0) return;

    this.timeoutId = setTimeout(() => {
      this.callback();
      this.maxExecutions--;
      this.remainingTime = this.delay;
      this.startTime = Date.now();
      this.scheduleNext();
    }, this.remainingTime);
  }

  pause(): void {
    if (!this.timeoutId || this.isPaused) return;

    clearTimeout(this.timeoutId);
    this.remainingTime -= Date.now() - this.startTime;
    this.isPaused = true;
  }

  resume(): void {
    if (!this.isPaused) return;

    this.startTime = Date.now();
    this.isPaused = false;
    this.scheduleNext();
  }

  stop(): void {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }
    this.remainingTime = this.delay;
    this.isPaused = false;
  }
}

Priority Queue Timeout ​

typescript
interface TimeoutTask {
  id: string;
  callback: () => void;
  delay: number;
  priority: number;
  createdAt: number;
}

class PriorityTimeout {
  private tasks = new Map<string, TimeoutTask>();
  private timeouts = new Map<string, NodeJS.Timeout>();

  schedule(
    id: string,
    callback: () => void,
    delay: number,
    priority: number = 0
  ): void {
    this.cancel(id);

    const task: TimeoutTask = {
      id,
      callback,
      delay,
      priority,
      createdAt: Date.now(),
    };

    this.tasks.set(id, task);
    this.scheduleTask(task);
  }

  private scheduleTask(task: TimeoutTask): void {
    const timeoutId = setTimeout(() => {
      this.tasks.delete(task.id);
      this.timeouts.delete(task.id);
      task.callback();
    }, task.delay);

    this.timeouts.set(task.id, timeoutId);
  }

  cancel(id: string): void {
    const timeoutId = this.timeouts.get(id);
    if (timeoutId) {
      clearTimeout(timeoutId);
      this.timeouts.delete(id);
      this.tasks.delete(id);
    }
  }

  rescheduleAll(): void {
    const tasks = Array.from(this.tasks.values());

    // Clear all existing timeouts
    this.timeouts.forEach((timeoutId) => clearTimeout(timeoutId));
    this.timeouts.clear();

    // Sort by priority (higher first) and creation time
    tasks.sort((a, b) => {
      if (a.priority !== b.priority) {
        return b.priority - a.priority;
      }
      return a.createdAt - b.createdAt;
    });

    // Reschedule with staggered delays
    tasks.forEach((task, index) => {
      const adjustedDelay = Math.max(
        0,
        task.delay - (Date.now() - task.createdAt)
      );
      const staggerDelay = index * 100; // Add 100ms between each task

      const timeoutId = setTimeout(() => {
        this.tasks.delete(task.id);
        this.timeouts.delete(task.id);
        task.callback();
      }, adjustedDelay + staggerDelay);

      this.timeouts.set(task.id, timeoutId);
    });
  }
}

Adaptive Timeout ​

typescript
interface AdaptiveTimeoutOptions {
  initialDelay: number;
  minDelay: number;
  maxDelay: number;
  factor: number;
  successThreshold: number;
  failureThreshold: number;
}

class AdaptiveTimeout {
  private currentDelay: number;
  private successCount: number = 0;
  private failureCount: number = 0;
  private timeoutId: NodeJS.Timeout | null = null;

  constructor(
    private callback: () => Promise<boolean>,
    private options: AdaptiveTimeoutOptions
  ) {
    this.currentDelay = options.initialDelay;
  }

  start(): void {
    this.schedule();
  }

  private schedule(): void {
    this.timeoutId = setTimeout(async () => {
      try {
        const success = await this.callback();

        if (success) {
          this.successCount++;
          this.failureCount = 0;

          if (this.successCount >= this.options.successThreshold) {
            this.decreaseDelay();
          }
        } else {
          this.failureCount++;
          this.successCount = 0;

          if (this.failureCount >= this.options.failureThreshold) {
            this.increaseDelay();
          }
        }
      } catch (error) {
        this.failureCount++;
        this.successCount = 0;

        if (this.failureCount >= this.options.failureThreshold) {
          this.increaseDelay();
        }
      }

      this.schedule();
    }, this.currentDelay);
  }

  private decreaseDelay(): void {
    this.currentDelay = Math.max(
      this.options.minDelay,
      this.currentDelay / this.options.factor
    );
    this.successCount = 0;
  }

  private increaseDelay(): void {
    this.currentDelay = Math.min(
      this.options.maxDelay,
      this.currentDelay * this.options.factor
    );
    this.failureCount = 0;
  }

  stop(): void {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }
  }

  reset(): void {
    this.stop();
    this.currentDelay = this.options.initialDelay;
    this.successCount = 0;
    this.failureCount = 0;
  }
}