Skip to main content

Overview

The async utilities help you control the timing and execution of functions, manage promises, and handle animations smoothly. These utilities are essential for performance optimization and creating responsive user interfaces.

Debouncing

debounce

Delays function execution until after a specified delay has elapsed since the last invocation.
import { debounce } from '@zayne-labs/toolkit-core';

// Basic usage
const handleSearch = debounce((query: string) => {
  console.log('Searching for:', query);
}, 300);

// User types rapidly
handleSearch('t');      // Not executed
handleSearch('ty');     // Not executed
handleSearch('typ');    // Not executed
handleSearch('type');   // Executed after 300ms
Type Signature:
type DebounceOptions = {
  maxWait?: number;
};

const debounce: <TCallbackFn extends AnyFunction>(
  callbackFn: TCallbackFn,
  delay: number | undefined,
  options?: DebounceOptions
) => DebouncedFn<TCallbackFn>

Features

const debouncedFn = debounce(() => {
  console.log('Executed after 500ms of inactivity');
}, 500);

debouncedFn(); // Starts timer
debouncedFn(); // Resets timer
debouncedFn(); // Resets timer again
// Executes once after 500ms of no calls

Common Use Cases

import { debounce } from '@zayne-labs/toolkit-core';

// Search input
const searchInput = document.querySelector<HTMLInputElement>('#search');
const debouncedSearch = debounce(async (query: string) => {
  const results = await fetch(`/api/search?q=${query}`);
  displayResults(await results.json());
}, 300);

searchInput?.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

// Window resize
const debouncedResize = debounce(() => {
  updateLayout();
}, 200, { maxWait: 500 });

window.addEventListener('resize', debouncedResize);

// Form validation
const debouncedValidate = debounce(async (email: string) => {
  const isValid = await validateEmail(email);
  showValidationResult(isValid);
}, 500);

emailInput.addEventListener('input', (e) => {
  debouncedValidate(e.target.value);
});
Debouncing is perfect for search inputs, form validation, and any scenario where you want to wait for the user to stop typing before executing an action.

Throttling

Limit how often a function can execute within a time window.

throttleByTimeout

Ensures a function executes at most once per delay period using setTimeout.
import { throttleByTimeout } from '@zayne-labs/toolkit-core';

const handleScroll = throttleByTimeout(() => {
  console.log('Scroll position:', window.scrollY);
}, 100);

window.addEventListener('scroll', handleScroll);

// Cancel if needed
handleScroll.cancelTimeout();
Type Signature:
const throttleByTimeout: <TCallbackFn extends AnyFunction>(
  callbackFn: TCallbackFn,
  delay: number
) => ThrottledByTimeoutFn<TCallbackFn>

throttleByTime

Throttles based on elapsed time without using timers (performance optimized).
import { throttleByTime } from '@zayne-labs/toolkit-core';

const trackMousePosition = throttleByTime((e: MouseEvent) => {
  console.log('Mouse at:', e.clientX, e.clientY);
  sendAnalytics({ x: e.clientX, y: e.clientY });
}, 1000);

document.addEventListener('mousemove', trackMousePosition);
Type Signature:
const throttleByTime: <TCallbackFn extends AnyFunction>(
  callbackFn: TCallbackFn,
  delay: number
) => ThrottledByTimeFn<TCallbackFn>
throttleByTime uses Date.now() for time tracking and doesn’t set timers, making it slightly more performant for high-frequency events.

throttleByFrame

Throttles function execution to at most once per animation frame.
import { throttleByFrame } from '@zayne-labs/toolkit-core';

const updateAnimation = throttleByFrame(() => {
  // Update DOM or canvas
  element.style.transform = `translateX(${position}px)`;
});

window.addEventListener('scroll', updateAnimation);

// Cancel pending frame
updateAnimation.cancelAnimation();
Type Signature:
const throttleByFrame: <TCallbackFn extends AnyFunction>(
  callbackFn: TCallbackFn
) => ThrottledByFrameFn<TCallbackFn>
throttleByFrame is ideal for scroll animations and visual updates as it aligns with the browser’s rendering pipeline (~60fps).

Throttle Comparison

Best for:
  • API calls
  • Analytics tracking
  • Save operations
Characteristics:
  • Uses setTimeout
  • Guarantees minimum delay between executions
  • Can be cancelled

Complete Examples

import { 
  throttleByTimeout, 
  throttleByTime, 
  throttleByFrame 
} from '@zayne-labs/toolkit-core';

// Infinite scroll with throttleByTimeout
const throttledLoadMore = throttleByTimeout(async () => {
  const nextPage = await fetchNextPage();
  appendItems(nextPage);
}, 1000);

window.addEventListener('scroll', () => {
  if (isNearBottom()) {
    throttledLoadMore();
  }
});

// Mouse tracking with throttleByTime
const throttledTracker = throttleByTime((e: MouseEvent) => {
  sendAnalytics({
    x: e.clientX,
    y: e.clientY,
    timestamp: Date.now()
  });
}, 2000);

document.addEventListener('mousemove', throttledTracker);

// Parallax effect with throttleByFrame
const parallaxElements = document.querySelectorAll('[data-parallax]');
const updateParallax = throttleByFrame(() => {
  const scrollY = window.scrollY;
  
  parallaxElements.forEach((el: HTMLElement) => {
    const speed = parseFloat(el.dataset.parallax ?? '0.5');
    el.style.transform = `translateY(${scrollY * speed}px)`;
  });
});

window.addEventListener('scroll', updateParallax);

Promise Utilities

createPromiseWithResolvers

Create a promise with externally accessible resolve/reject functions.
import { createPromiseWithResolvers } from '@zayne-labs/toolkit-core';

// Basic usage
const { promise, resolve, reject } = createPromiseWithResolvers<string>();

// Resolve from anywhere
setTimeout(() => resolve('Success!'), 1000);

await promise; // 'Success!'
Type Signature:
type BasePromiseOptions = {
  signal?: AbortSignal;
};

const createPromiseWithResolvers: <TPromise>(
  options?: BasePromiseOptions
) => PromiseWithResolvers<TPromise>

With AbortSignal

import { createPromiseWithResolvers } from '@zayne-labs/toolkit-core';

const controller = new AbortController();

const { promise, resolve } = createPromiseWithResolvers<Response>({
  signal: controller.signal
});

// Start async operation
fetch('/api/data')
  .then(resolve)
  .catch(() => {});

// Abort after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  await promise;
} catch (error) {
  console.log('Request aborted or failed');
}

Advanced Example: Custom Timeout

import { createPromiseWithResolvers } from '@zayne-labs/toolkit-core';

const withTimeout = async <T>(
  promise: Promise<T>,
  timeoutMs: number
): Promise<T> => {
  const { promise: timeoutPromise, reject } = createPromiseWithResolvers<T>();
  
  const timeoutId = setTimeout(() => {
    reject(new Error(`Timeout after ${timeoutMs}ms`));
  }, timeoutMs);
  
  try {
    const result = await Promise.race([promise, timeoutPromise]);
    clearTimeout(timeoutId);
    return result;
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
};

// Usage
try {
  const data = await withTimeout(
    fetch('/api/slow-endpoint'),
    3000
  );
} catch (error) {
  console.error('Request timed out');
}
This utility uses the native Promise.withResolvers() when available (modern browsers), with a polyfill fallback for older environments.

Timing Utilities

waitFor

Asynchronously wait for a specified duration.
import { waitFor } from '@zayne-labs/toolkit-core';

// Wait in milliseconds
await waitFor(1000);
console.log('1 second later');

// Wait in seconds
await waitFor({ seconds: 2 });
console.log('2 seconds later');

// Wait in milliseconds (object syntax)
await waitFor({ milliseconds: 500 });
console.log('500ms later');

// Zero delay returns immediately
await waitFor(0); // No wait
await waitFor({ seconds: 0 }); // No wait
Type Signature:
type Delay = 
  | { milliseconds: number }
  | { seconds: number };

const waitFor: (
  delay: number | Delay
) => Promise<void> | void

waitForSync

Synchronously block execution for a duration (use sparingly).
import { waitForSync } from '@zayne-labs/toolkit-core';

// Blocks the thread for 1 second
waitForSync(1000);
console.log('This runs after 1 second of blocking');

// With seconds
waitForSync({ seconds: 0.5 });
waitForSync blocks the JavaScript thread and should be used very sparingly. It can freeze the UI and should generally be avoided in production code. Prefer waitFor for async waits.

Practical Examples

import { waitFor } from '@zayne-labs/toolkit-core';

// Retry with exponential backoff
const fetchWithRetry = async (url: string, maxRetries = 3) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch(url);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Wait with exponential backoff
      await waitFor({ milliseconds: Math.pow(2, i) * 1000 });
      console.log(`Retrying... attempt ${i + 2}`);
    }
  }
};

// Delayed execution
const delayedNotification = async (message: string, delay: number) => {
  await waitFor(delay);
  showNotification(message);
};

await delayedNotification('Your session will expire soon', 5000);

// Staggered animations
const animateSequence = async (elements: HTMLElement[]) => {
  for (const element of elements) {
    element.classList.add('fade-in');
    await waitFor(200); // 200ms between each
  }
};

Animation Utilities

setAnimationInterval

Create smooth, frame-based intervals using requestAnimationFrame.
import { setAnimationInterval } from '@zayne-labs/toolkit-core';

const { start, stop } = setAnimationInterval(
  () => {
    console.log('Every 1000ms');
    updateAnimation();
  },
  1000,
  {
    immediate: true, // Start immediately
    once: false      // Repeat indefinitely
  }
);

// Start the interval
start();

// Stop when done
stop();
Type Signature:
type AnimationIntervalOptions = {
  immediate?: boolean;
  once?: boolean;
};

const setAnimationInterval: (
  onAnimation: () => void,
  intervalDuration: number | null,
  options?: AnimationIntervalOptions
) => {
  start: () => void;
  stop: () => void;
}

Configuration Options

Start the interval immediately when created.
const { start, stop } = setAnimationInterval(
  updateFrame,
  16, // ~60fps
  { immediate: true }
);
// Animation starts right away

Advanced Examples

import { setAnimationInterval } from '@zayne-labs/toolkit-core';

// Smooth counter
let count = 0;
const maxCount = 100;

const counter = setAnimationInterval(
  () => {
    count++;
    updateDisplay(count);
    
    if (count >= maxCount) {
      counter.stop();
    }
  },
  50,
  { immediate: true }
);

// Game loop
class Game {
  private gameLoop: ReturnType<typeof setAnimationInterval>;
  
  constructor() {
    this.gameLoop = setAnimationInterval(
      () => this.update(),
      16 // ~60fps
    );
  }
  
  start() {
    this.gameLoop.start();
  }
  
  stop() {
    this.gameLoop.stop();
  }
  
  update() {
    // Update game state
    this.updatePhysics();
    this.render();
  }
}

// Progress indicator
const animateProgress = (element: HTMLElement, duration: number) => {
  let progress = 0;
  const increment = 100 / (duration / 16); // Assuming ~60fps
  
  const animation = setAnimationInterval(
    () => {
      progress += increment;
      element.style.width = `${Math.min(progress, 100)}%`;
      
      if (progress >= 100) {
        animation.stop();
      }
    },
    16,
    { immediate: true }
  );
  
  return animation;
};

const progressBar = document.querySelector('.progress');
animateProgress(progressBar, 2000); // Animate over 2 seconds
setAnimationInterval uses requestAnimationFrame internally, making it more efficient than setInterval for animations and providing smoother 60fps updates.

Best Practices

Use Debounce when:
  • You want to wait for user to finish an action (search, form input)
  • The last value/call is what matters
  • You can afford to wait for inactivity
Use Throttle when:
  • You need regular updates (scroll position, mouse tracking)
  • You want guaranteed execution intervals
  • Every nth execution matters, not just the last
Example:
// Debounce for search
const search = debounce(query => api.search(query), 300);

// Throttle for scroll tracking
const trackScroll = throttleByFrame(() => {
  analytics.track('scroll', window.scrollY);
});

Build docs developers (and LLMs) love