javascript

examples

examples.js
/**
 * Async Patterns - Examples
 * Common patterns for managing asynchronous operations
 */

// Utility function for simulating async operations
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function asyncTask(id, duration = 100) {
  console.log(`Task ${id}: started`);
  await delay(duration);
  console.log(`Task ${id}: completed`);
  return `Result ${id}`;
}

// =============================================================================
// 1. SEQUENTIAL EXECUTION
// =============================================================================

console.log('--- Sequential Execution ---');

async function processSequential(items) {
  const results = [];
  console.time('Sequential');

  for (const item of items) {
    const result = await asyncTask(item, 100);
    results.push(result);
  }

  console.timeEnd('Sequential');
  return results;
}

// Note: Takes items.length * 100ms
// processSequential([1, 2, 3, 4, 5]);

// =============================================================================
// 2. PARALLEL EXECUTION
// =============================================================================

console.log('\n--- Parallel Execution ---');

async function processParallel(items) {
  console.time('Parallel');

  const results = await Promise.all(items.map((item) => asyncTask(item, 100)));

  console.timeEnd('Parallel');
  return results;
}

// Note: Takes max(100, 100, ...) = 100ms
// processParallel([1, 2, 3, 4, 5]);

// =============================================================================
// 3. BATCHED EXECUTION
// =============================================================================

console.log('\n--- Batched Execution ---');

async function processBatched(items, batchSize = 2) {
  const results = [];
  console.time('Batched');

  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    console.log(`Processing batch: ${batch.join(', ')}`);

    const batchResults = await Promise.all(
      batch.map((item) => asyncTask(item, 100))
    );

    results.push(...batchResults);
  }

  console.timeEnd('Batched');
  return results;
}

// Note: Takes ceiling(items.length / batchSize) * 100ms
// processBatched([1, 2, 3, 4, 5], 2);

// =============================================================================
// 4. CONCURRENT LIMIT
// =============================================================================

console.log('\n--- Concurrent Limit ---');

async function withConcurrencyLimit(tasks, limit) {
  const results = [];
  const executing = [];

  for (const task of tasks) {
    const promise = Promise.resolve().then(task);
    results.push(promise);

    if (limit <= tasks.length) {
      const executing = promise.then(() =>
        executing.splice(executing.indexOf(executing), 1)
      );
      executing.push(executing);

      if (executing.length >= limit) {
        await Promise.race(executing);
      }
    }
  }

  return Promise.all(results);
}

// Better implementation with a pool
class ConcurrencyPool {
  constructor(limit) {
    this.limit = limit;
    this.running = 0;
    this.queue = [];
  }

  async run(task) {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          resolve(await task());
        } catch (error) {
          reject(error);
        }
      });
      this.tryRun();
    });
  }

  async tryRun() {
    while (this.running < this.limit && this.queue.length > 0) {
      const task = this.queue.shift();
      this.running++;

      try {
        await task();
      } finally {
        this.running--;
        this.tryRun();
      }
    }
  }
}

async function demonstrateConcurrencyPool() {
  console.log('\n--- Concurrency Pool Demo ---');
  const pool = new ConcurrencyPool(2);

  const tasks = [1, 2, 3, 4, 5].map((id) => pool.run(() => asyncTask(id, 100)));

  console.time('Pool');
  const results = await Promise.all(tasks);
  console.timeEnd('Pool');
  console.log('Results:', results);
}

// =============================================================================
// 5. PIPELINE PATTERN
// =============================================================================

console.log('\n--- Pipeline Pattern ---');

// Simple pipeline
async function pipeline(value, ...fns) {
  let result = value;
  for (const fn of fns) {
    result = await fn(result);
  }
  return result;
}

// Pipe function (creates reusable pipeline)
function pipe(...fns) {
  return async (value) => {
    let result = value;
    for (const fn of fns) {
      result = await fn(result);
    }
    return result;
  };
}

// Pipeline stages
const fetchUser = async (id) => {
  await delay(50);
  return { id, name: `User ${id}` };
};

const enrichWithPosts = async (user) => {
  await delay(50);
  return { ...user, posts: ['Post 1', 'Post 2'] };
};

const formatOutput = async (user) => {
  await delay(50);
  return `${user.name} has ${user.posts.length} posts`;
};

// Use pipeline
async function demoPipeline() {
  const result = await pipeline(1, fetchUser, enrichWithPosts, formatOutput);
  console.log('Pipeline result:', result);

  // Create reusable pipeline
  const getUserSummary = pipe(fetchUser, enrichWithPosts, formatOutput);
  console.log('Reusable pipeline:', await getUserSummary(2));
}

// =============================================================================
// 6. DEBOUNCE
// =============================================================================

console.log('\n--- Debounce ---');

function debounce(fn, wait) {
  let timeout;

  return function (...args) {
    const later = () => {
      timeout = null;
      fn.apply(this, args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Async debounce with Promise
function debounceAsync(fn, wait) {
  let timeout;
  let pendingPromise;
  let resolver;

  return function (...args) {
    return new Promise((resolve, reject) => {
      clearTimeout(timeout);
      resolver = { resolve, reject };

      timeout = setTimeout(async () => {
        try {
          const result = await fn.apply(this, args);
          resolver.resolve(result);
        } catch (error) {
          resolver.reject(error);
        }
      }, wait);
    });
  };
}

// Demo
const debouncedLog = debounce((msg) => console.log('Debounced:', msg), 200);

function demoDebounce() {
  console.log('Firing rapid events...');
  debouncedLog('first');
  debouncedLog('second');
  debouncedLog('third'); // Only this will fire
}

// =============================================================================
// 7. THROTTLE
// =============================================================================

console.log('\n--- Throttle ---');

function throttle(fn, limit) {
  let inThrottle = false;
  let lastArgs = null;

  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;

      setTimeout(() => {
        inThrottle = false;
        if (lastArgs) {
          fn.apply(this, lastArgs);
          lastArgs = null;
        }
      }, limit);
    } else {
      lastArgs = args;
    }
  };
}

// Throttle with trailing call
function throttleWithTrailing(fn, limit) {
  let waiting = false;
  let lastArgs = null;

  const timeoutFn = () => {
    if (lastArgs) {
      fn.apply(this, lastArgs);
      lastArgs = null;
      waiting = true;
      setTimeout(timeoutFn, limit);
    } else {
      waiting = false;
    }
  };

  return function (...args) {
    if (!waiting) {
      fn.apply(this, args);
      waiting = true;
      setTimeout(timeoutFn, limit);
    } else {
      lastArgs = args;
    }
  };
}

const throttledLog = throttle((msg) => console.log('Throttled:', msg), 500);

// =============================================================================
// 8. RETRY PATTERN
// =============================================================================

console.log('\n--- Retry Pattern ---');

async function retry(fn, { maxAttempts = 3, delay = 100, backoff = 2 } = {}) {
  let lastError;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn(attempt);
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${attempt} failed: ${error.message}`);

      if (attempt < maxAttempts) {
        const waitTime = delay * Math.pow(backoff, attempt - 1);
        await new Promise((r) => setTimeout(r, waitTime));
      }
    }
  }

  throw lastError;
}

// With jitter
async function retryWithJitter(fn, options = {}) {
  const { maxAttempts = 3, baseDelay = 100, maxDelay = 5000 } = options;
  let lastError;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn(attempt);
    } catch (error) {
      lastError = error;

      if (attempt < maxAttempts) {
        // Exponential backoff with jitter
        const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
        const jitter = Math.random() * exponentialDelay * 0.5;
        const waitTime = Math.min(exponentialDelay + jitter, maxDelay);

        await new Promise((r) => setTimeout(r, waitTime));
      }
    }
  }

  throw lastError;
}

// =============================================================================
// 9. TIMEOUT PATTERN
// =============================================================================

console.log('\n--- Timeout Pattern ---');

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
  });

  return Promise.race([promise, timeout]);
}

// With cancellation
function withTimeoutAndCancel(asyncFn, ms) {
  const controller = new AbortController();

  const timeout = setTimeout(() => controller.abort(), ms);

  return asyncFn(controller.signal).finally(() => clearTimeout(timeout));
}

async function demoTimeout() {
  const slowTask = delay(500).then(() => 'slow result');

  try {
    const result = await withTimeout(slowTask, 200);
    console.log('Result:', result);
  } catch (error) {
    console.log('Timeout caught:', error.message);
  }
}

// =============================================================================
// 10. POLLING PATTERN
// =============================================================================

console.log('\n--- Polling Pattern ---');

async function poll(fn, { interval = 1000, timeout = 30000, condition } = {}) {
  const startTime = Date.now();

  while (true) {
    const result = await fn();

    if (condition(result)) {
      return result;
    }

    if (Date.now() - startTime > timeout) {
      throw new Error('Polling timeout');
    }

    await delay(interval);
  }
}

// Exponential polling
async function pollWithBackoff(fn, options = {}) {
  const {
    initialInterval = 1000,
    maxInterval = 30000,
    factor = 1.5,
    timeout = 60000,
    condition = (result) => result.done,
  } = options;

  const startTime = Date.now();
  let interval = initialInterval;

  while (true) {
    const result = await fn();

    if (condition(result)) {
      return result;
    }

    if (Date.now() - startTime > timeout) {
      throw new Error('Polling timeout');
    }

    await delay(interval);
    interval = Math.min(interval * factor, maxInterval);
  }
}

// =============================================================================
// 11. QUEUE PATTERN
// =============================================================================

console.log('\n--- Queue Pattern ---');

class AsyncQueue {
  constructor(concurrency = 1) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  enqueue(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    while (this.running < this.concurrency && this.queue.length > 0) {
      const { task, resolve, reject } = this.queue.shift();
      this.running++;

      try {
        const result = await task();
        resolve(result);
      } catch (error) {
        reject(error);
      } finally {
        this.running--;
        this.processQueue();
      }
    }
  }

  get length() {
    return this.queue.length;
  }

  get pending() {
    return this.running;
  }
}

// =============================================================================
// 12. MEMOIZATION WITH ASYNC
// =============================================================================

console.log('\n--- Async Memoization ---');

function memoizeAsync(fn, keyFn = (...args) => JSON.stringify(args)) {
  const cache = new Map();

  return async function (...args) {
    const key = keyFn(...args);

    if (cache.has(key)) {
      console.log('Cache hit:', key);
      return cache.get(key);
    }

    console.log('Cache miss:', key);
    const result = await fn.apply(this, args);
    cache.set(key, result);

    return result;
  };
}

// With TTL
function memoizeAsyncWithTTL(fn, ttl = 5000) {
  const cache = new Map();

  return async function (...args) {
    const key = JSON.stringify(args);
    const cached = cache.get(key);

    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.value;
    }

    const value = await fn.apply(this, args);
    cache.set(key, { value, timestamp: Date.now() });

    return value;
  };
}

// =============================================================================
// RUN DEMONSTRATIONS
// =============================================================================

async function runDemos() {
  console.log('\n========= Running Demonstrations =========\n');

  console.log('1. Sequential vs Parallel:');
  await processSequential([1, 2, 3]);
  await processParallel([1, 2, 3]);

  console.log('\n2. Batched:');
  await processBatched([1, 2, 3, 4, 5], 2);

  console.log('\n3. Pipeline:');
  await demoPipeline();

  console.log('\n4. Debounce:');
  demoDebounce();

  console.log('\n5. Timeout:');
  await demoTimeout();

  console.log('\n========= Demonstrations Complete =========');
}

// Run demos
runDemos();
Examples - JavaScript Tutorial | DeepML