DeesseJS FP

Sleep

Delayed execution utilities with jitter and AbortController support

The sleep module provides utilities for deliberate delays, which are essential when you need to implement rate limiting, debouncing, or retry backoff strategies in your applications.

Why Use Sleep?

Sometimes your code needs to wait before proceeding. This is useful when you need to respect rate limits imposed by external APIs, implement debouncing for user input, or create spacing between operations that would otherwise overwhelm your system.

Here is how you can use sleep to prevent hitting rate limits when updating multiple users. Instead of sending all requests immediately, you insert a delay between each operation.

rate-limiting-example.ts
import { sleep } from '@deessejs/fp';

// Without sleep, all requests fire immediately
const results = await Promise.all(
  users.map(user => api.updateUser(user)) // This might hit rate limit
);

// With sleep, your code respects API limits
for (const user of users) {
  await api.updateUser(user);
  await sleep(100); // Wait 100ms between requests
}

The Sleep Function

The sleep function creates a Promise that resolves after a specified delay. You can use this when you need to pause execution for a known, fixed amount of time.

This example shows a simple delay of one second, followed by a delay that can be cancelled using an AbortSignal.

basic-sleep.ts
import { sleep } from '@deessejs/fp';

// Simple delay
await sleep(1000); // Wait 1 second

// With AbortSignal
const controller = new AbortController();
await sleep(5000, { signal: controller.signal });

Parameters

The sleep function accepts the following parameters:

ParameterTypeRequiredDefaultDescription
millisnumberYes-The duration to sleep in milliseconds. The value is clamped to the platform maximum, which is approximately 24.8 days on most systems.
optionsSleepOptionsNo{}An optional configuration object that controls sleep behavior.

Options Object

The options parameter contains the following properties:

PropertyTypeDefaultDescription
jitterboolean | numberundefinedWhen set to true, adds random jitter to prevent thundering herd problems. When set to a number, uses that value as the jitter magnitude.
signalAbortSignalundefinedAn external AbortSignal that allows you to cancel the sleep operation before it completes.

The Sleep Until Function

The sleepUntil function pauses execution until a predicate returns true, or until a timeout is reached. This is particularly useful when you need to poll for a condition to become true, such as waiting for a service to become healthy or a resource to become available.

This example demonstrates polling a status endpoint until it returns a successful response, with a maximum wait time of 5 seconds and checks every 100 milliseconds.

poll-until-ready.ts
import { sleepUntil } from '@deessejs/fp';

// Poll until condition is met (max 5 seconds)
const ready = await sleepUntil(
  () => fetchStatus().ok,
  { timeout: 5000, interval: 100 }
);

// Async predicate
const ready = await sleepUntil(
  async () => {
    const status = await fetchStatus();
    return status.ok;
  },
  { timeout: 5000, interval: 100 }
);

Parameters

The sleepUntil function accepts the following parameters:

ParameterTypeRequiredDefaultDescription
predicate() => boolean | Promise<boolean>Yes-A function that returns a boolean (or a Promise that resolves to a boolean) indicating whether the condition has been met.
optionsobjectYes-Configuration options for the polling behavior.

Options Object

The options parameter contains the following properties:

PropertyTypeDefaultDescription
timeoutnumberInfinityThe maximum time to wait in milliseconds before giving up. Set to Infinity to wait indefinitely.
intervalnumber10The time in milliseconds between each poll attempt. Lower values poll more frequently but use more CPU.
signalAbortSignalundefinedAn external AbortSignal that allows you to cancel the polling operation.

Return Value

The function returns a Promise<boolean> that resolves to true if the predicate succeeded before the timeout, or false if the timeout was reached or the operation was aborted.

The Sleep Random Function

The sleepRandom function pauses execution for a random duration within a specified range. This is particularly useful when you need to add jitter to prevent thundering herd problems, where many clients simultaneously retry at the same time after a failure.

This example shows how to sleep for a random duration between 100 and 500 milliseconds, which is useful for adding randomness to retry logic.

random-delay.ts
import { sleepRandom } from '@deessejs/fp';

// Sleep for 100-500ms (useful for jitter)
const actualDuration = await sleepRandom(100, 500);

Parameters

The sleepRandom function accepts the following parameters:

ParameterTypeRequiredDefaultDescription
minnumberYes-The minimum sleep duration in milliseconds. Must be zero or greater.
maxnumberYes-The maximum sleep duration in milliseconds. Must be greater than or equal to min.
optionsSleepOptionsNo{}An optional configuration object with jitter and signal properties.

Return Value

The function returns a Promise<number> that resolves to the actual time slept in milliseconds.

Validation Rules

The function enforces the following validation rules:

  • If min is less than zero, a ValidationError is thrown.
  • If max is less than zero, a ValidationError is thrown.
  • If min is greater than max, a ValidationError is thrown.

Sleep Options Interface

The SleepOptions interface defines the configuration options that can be passed to sleep functions. This interface provides a standardized way to control cancellation and jitter behavior across all sleep utilities.

sleep-options.ts
interface SleepOptions {
  /** Add jitter to prevent thundering herd */
  jitter?: boolean | number;

  /** AbortSignal for cancellation */
  signal?: AbortSignal;
}

Cancellation

All sleep operations can be cancelled using an AbortSignal. This is useful when you need to implement timeouts, clean up on component unmount, or abort operations based on user action.

This example shows how to create a sleep that can be cancelled after a timeout. The controller is aborted after 1 second, causing the 5-second sleep to reject with an AbortError.

cancellable-sleep.ts
import { sleep } from '@deessejs/fp';

const controller = new AbortController();

try {
  await sleep(5000, { signal: controller.signal });
  console.log('Sleep completed');
} catch (error) {
  if (error._tag === 'AbortError') {
    console.log('Sleep was cancelled');
  }
}

// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);

Edge Cases

The sleep functions handle several edge cases gracefully:

ScenarioBehavior
sleep(0)Yields to the event loop and resolves on the next tick, allowing other pending operations to run.
Negative durationTreated as zero, so no waiting occurs.
Duration exceeds platform maximumClamped to the platform maximum, which is approximately 24.8 days on most systems.
Abort before startIf the signal is already aborted when sleep begins, the function resolves immediately.
Abort during sleepThe function rejects with an AbortError.
sleepRandom(min, max) with min > maxThrows a ValidationError.

Real-World Examples

Rate-Limited API Calls

When you need to fetch multiple resources from an API that enforces rate limits, you can use sleep to add delays between requests. This prevents your application from being throttled or temporarily blocked by the API.

The following example shows a function that fetches multiple URLs with a configurable delay between each request.

rate-limited-fetch.ts
import { sleep } from '@deessejs/fp';

const fetchWithRateLimit = async <T>(
  urls: string[],
  delay: number
): Promise<T[]> => {
  const results: T[] = [];

  for (let i = 0; i < urls.length; i++) {
    results.push(await fetch(urls[i]) as T);
    if (i < urls.length - 1) {
      await sleep(delay);
    }
  }

  return results;
};

Polling for Condition

When you need to wait for an external service to become ready, such as a database, cache, or microservice, you can use sleepUntil to poll until the condition is met or a timeout is reached.

This example shows how to wait for a service to become healthy before proceeding with further operations.

wait-for-service.ts
import { sleepUntil } from '@deessejs/fp';

const waitForService = async (serviceId: string, deadline: number) => {
  const ready = await sleepUntil(
    async () => {
      const status = await checkServiceHealth(serviceId);
      return status === 'healthy';
    },
    { timeout: deadline, interval: 500 }
  );

  return ready
    ? { success: true }
    : { success: false, reason: 'timeout' };
};

Jittered Backoff

When implementing retry logic, you can use sleepRandom to add jitter to your backoff strategy. This prevents multiple clients from retrying simultaneously after a failure, which could overwhelm the system again.

This example shows a simple exponential backoff with jitter, where the delay is randomly selected from a range that doubles with each iteration up to a maximum.

jittered-backoff.ts
import { sleepRandom } from '@deessejs/fp';

const jitteredBackoff = async (baseDelay: number, maxDelay: number) => {
  const delay = Math.min(baseDelay * 2, maxDelay);
  const actual = await sleepRandom(delay / 2, delay);
  return actual;
};

See Also

On this page