javascript

examples

examples.js
/**
 * ========================================
 * 9.1 CALLBACKS - CODE EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Basic Callback
 * A function that takes another function as argument
 */
console.log('--- Example 1: Basic Callback ---');

function greet(name, callback) {
  console.log(`Hello, ${name}!`);
  callback();
}

function afterGreeting() {
  console.log('Nice to meet you!');
}

greet('Alice', afterGreeting);

// With anonymous function
greet('Bob', function () {
  console.log('How are you?');
});

// With arrow function
greet('Charlie', () => console.log('Welcome!'));

/**
 * EXAMPLE 2: Synchronous Callbacks
 * Callbacks that execute immediately
 */
console.log('\n--- Example 2: Synchronous Callbacks ---');

const numbers = [1, 2, 3, 4, 5];

// forEach - synchronous callback
console.log('forEach:');
numbers.forEach((num, index) => {
  console.log(`  Index ${index}: ${num}`);
});

// map - returns new array
const doubled = numbers.map((n) => n * 2);
console.log('Doubled:', doubled);

// filter - returns filtered array
const evens = numbers.filter((n) => n % 2 === 0);
console.log('Evens:', evens);

// reduce - accumulates value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log('Sum:', sum);

// find - returns first match
const found = numbers.find((n) => n > 3);
console.log('First > 3:', found);

/**
 * EXAMPLE 3: setTimeout - Asynchronous Callback
 */
console.log('\n--- Example 3: setTimeout ---');

console.log('Before setTimeout');

setTimeout(() => {
  console.log('Inside setTimeout (1 second later)');
}, 1000);

console.log('After setTimeout (runs immediately!)');

// Zero delay still async
setTimeout(() => {
  console.log('Zero delay - but still async!');
}, 0);

console.log('This runs before zero delay!');

/**
 * EXAMPLE 4: setInterval - Repeated Callbacks
 */
console.log('\n--- Example 4: setInterval ---');

let count = 0;
const maxCount = 3;

const intervalId = setInterval(() => {
  count++;
  console.log(`Interval tick #${count}`);

  if (count >= maxCount) {
    clearInterval(intervalId);
    console.log('Interval cleared!');
  }
}, 500);

/**
 * EXAMPLE 5: Callback with Parameters
 */
console.log('\n--- Example 5: Callback with Parameters ---');

function fetchData(url, callback) {
  console.log(`Fetching data from: ${url}`);

  // Simulate async operation
  setTimeout(() => {
    const data = { id: 1, name: 'Sample Data', url };
    callback(data);
  }, 100);
}

fetchData('https://api.example.com/users', (result) => {
  console.log('Received:', result);
});

/**
 * EXAMPLE 6: Error-First Callback Pattern
 */
console.log('\n--- Example 6: Error-First Callbacks ---');

function divideAsync(a, b, callback) {
  setTimeout(() => {
    if (b === 0) {
      callback(new Error('Division by zero'), null);
    } else {
      callback(null, a / b);
    }
  }, 100);
}

// Success case
divideAsync(10, 2, (error, result) => {
  if (error) {
    console.error('Error:', error.message);
    return;
  }
  console.log('10 / 2 =', result);
});

// Error case
divideAsync(10, 0, (error, result) => {
  if (error) {
    console.error('Error:', error.message);
    return;
  }
  console.log('Result:', result);
});

/**
 * EXAMPLE 7: Multiple Callbacks (Success/Error)
 */
console.log('\n--- Example 7: Success/Error Callbacks ---');

function validateUser(user, onSuccess, onError) {
  setTimeout(() => {
    if (!user.name) {
      onError(new Error('Name is required'));
    } else if (!user.email) {
      onError(new Error('Email is required'));
    } else if (!user.email.includes('@')) {
      onError(new Error('Invalid email format'));
    } else {
      onSuccess({ ...user, validated: true });
    }
  }, 100);
}

// Valid user
validateUser(
  { name: 'Alice', email: 'alice@example.com' },
  (user) => console.log('Valid user:', user),
  (error) => console.error('Validation failed:', error.message)
);

// Invalid user
validateUser(
  { name: 'Bob', email: 'invalid' },
  (user) => console.log('Valid user:', user),
  (error) => console.error('Validation failed:', error.message)
);

/**
 * EXAMPLE 8: Callback Hell (Anti-pattern)
 */
console.log('\n--- Example 8: Callback Hell ---');

// Simulated async operations
function getUser(id, callback) {
  setTimeout(() => callback(null, { id, name: 'John' }), 50);
}

function getOrders(userId, callback) {
  setTimeout(() => callback(null, [{ id: 1, product: 'Laptop' }]), 50);
}

function getOrderDetails(orderId, callback) {
  setTimeout(() => callback(null, { id: orderId, price: 999 }), 50);
}

// ❌ Callback Hell - hard to read and maintain
getUser(1, (error, user) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log('User:', user);

  getOrders(user.id, (error, orders) => {
    if (error) {
      console.error(error);
      return;
    }
    console.log('Orders:', orders);

    getOrderDetails(orders[0].id, (error, details) => {
      if (error) {
        console.error(error);
        return;
      }
      console.log('Details:', details);
    });
  });
});

/**
 * EXAMPLE 9: Avoiding Callback Hell - Named Functions
 */
console.log('\n--- Example 9: Named Functions Solution ---');

function handleUser(error, user) {
  if (error) {
    console.error('User error:', error);
    return;
  }
  console.log('Got user:', user.name);
  getOrders(user.id, handleOrders);
}

function handleOrders(error, orders) {
  if (error) {
    console.error('Orders error:', error);
    return;
  }
  console.log('Got orders:', orders.length);
  getOrderDetails(orders[0].id, handleDetails);
}

function handleDetails(error, details) {
  if (error) {
    console.error('Details error:', error);
    return;
  }
  console.log('Got details:', details);
}

// ✅ Clean, flat structure
getUser(1, handleUser);

/**
 * EXAMPLE 10: Callback Wrapper Utility
 */
console.log('\n--- Example 10: Callback Wrapper ---');

// Utility to handle error-first callbacks
function handleCallback(onSuccess, onError = console.error) {
  return function (error, result) {
    if (error) {
      onError(error);
    } else {
      onSuccess(result);
    }
  };
}

// Usage
getUser(
  1,
  handleCallback(
    (user) => console.log('Wrapped callback - User:', user),
    (error) => console.error('Wrapped callback - Error:', error)
  )
);

/**
 * EXAMPLE 11: Parallel Execution with Callbacks
 */
console.log('\n--- Example 11: Parallel Execution ---');

function fetchMultiple(urls, callback) {
  const results = [];
  let completed = 0;
  let hasError = false;

  urls.forEach((url, index) => {
    // Simulate fetch
    setTimeout(() => {
      if (hasError) return;

      if (url.includes('error')) {
        hasError = true;
        callback(new Error(`Failed: ${url}`), null);
        return;
      }

      results[index] = `Data from ${url}`;
      completed++;

      if (completed === urls.length) {
        callback(null, results);
      }
    }, Math.random() * 200);
  });
}

fetchMultiple(
  ['/api/users', '/api/posts', '/api/comments'],
  (error, results) => {
    if (error) {
      console.error('Parallel error:', error.message);
      return;
    }
    console.log('Parallel results:', results);
  }
);

/**
 * EXAMPLE 12: Sequential Execution with Callbacks
 */
console.log('\n--- Example 12: Sequential Execution ---');

function runSequence(tasks, finalCallback) {
  let index = 0;
  const results = [];

  function next(error, result) {
    if (error) {
      finalCallback(error, null);
      return;
    }

    if (result !== undefined) {
      results.push(result);
    }

    if (index >= tasks.length) {
      finalCallback(null, results);
      return;
    }

    const task = tasks[index++];
    task(next);
  }

  next();
}

// Define tasks
const tasks = [
  (cb) => setTimeout(() => cb(null, 'Task 1 done'), 100),
  (cb) => setTimeout(() => cb(null, 'Task 2 done'), 50),
  (cb) => setTimeout(() => cb(null, 'Task 3 done'), 75),
];

runSequence(tasks, (error, results) => {
  if (error) {
    console.error('Sequence error:', error);
    return;
  }
  console.log('Sequence results:', results);
});

/**
 * EXAMPLE 13: Debounce Function
 */
console.log('\n--- Example 13: Debounce ---');

function debounce(func, delay) {
  let timeoutId;

  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// Simulated search function
const search = debounce((query) => {
  console.log(`Searching for: ${query}`);
}, 300);

// Rapid calls - only last one executes
search('a');
search('ap');
search('app');
search('appl');
search('apple'); // Only this runs after 300ms

/**
 * EXAMPLE 14: Throttle Function
 */
console.log('\n--- Example 14: Throttle ---');

function throttle(func, limit) {
  let inThrottle = false;

  return function (...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// Simulated scroll handler
const onScroll = throttle((position) => {
  console.log(`Scroll position: ${position}`);
}, 200);

// Simulate rapid scroll events
for (let i = 0; i < 10; i++) {
  onScroll(i * 100);
}

/**
 * EXAMPLE 15: Once Function
 */
console.log('\n--- Example 15: Once (Single Execution) ---');

function once(func) {
  let called = false;
  let result;

  return function (...args) {
    if (!called) {
      called = true;
      result = func.apply(this, args);
    }
    return result;
  };
}

const initialize = once(() => {
  console.log('Initialization complete!');
  return { initialized: true };
});

console.log(initialize()); // Runs function
console.log(initialize()); // Returns cached result
console.log(initialize()); // Returns cached result

/**
 * EXAMPLE 16: Retry with Callbacks
 */
console.log('\n--- Example 16: Retry Pattern ---');

function retry(operation, maxAttempts, delay, callback) {
  let attempts = 0;

  function attempt() {
    attempts++;
    console.log(`Attempt ${attempts}/${maxAttempts}`);

    operation((error, result) => {
      if (error) {
        if (attempts >= maxAttempts) {
          callback(new Error(`Failed after ${maxAttempts} attempts`), null);
        } else {
          setTimeout(attempt, delay);
        }
      } else {
        callback(null, result);
      }
    });
  }

  attempt();
}

// Simulated flaky operation
let failCount = 0;
function flakyOperation(callback) {
  failCount++;
  if (failCount < 3) {
    callback(new Error('Temporary failure'), null);
  } else {
    callback(null, 'Success!');
  }
}

retry(flakyOperation, 5, 100, (error, result) => {
  if (error) {
    console.error('Retry failed:', error.message);
  } else {
    console.log('Retry succeeded:', result);
  }
});

/**
 * EXAMPLE 17: Event Emitter Pattern
 */
console.log('\n--- Example 17: Event Emitter ---');

class SimpleEventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return () => this.off(event, callback);
  }

  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter((cb) => cb !== callback);
    }
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach((callback) => {
        callback(...args);
      });
    }
  }

  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

const emitter = new SimpleEventEmitter();

emitter.on('message', (msg) => console.log('Handler 1:', msg));
emitter.on('message', (msg) => console.log('Handler 2:', msg));
emitter.once('connect', () => console.log('Connected! (only once)'));

emitter.emit('message', 'Hello!');
emitter.emit('connect');
emitter.emit('connect'); // No output - handler removed

/**
 * EXAMPLE 18: Callback Queue
 */
console.log('\n--- Example 18: Callback Queue ---');

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

  add(task, callback) {
    this.queue.push({ task, callback });
    this.process();
  }

  process() {
    while (this.running < this.concurrency && this.queue.length > 0) {
      const { task, callback } = this.queue.shift();
      this.running++;

      task((error, result) => {
        this.running--;
        callback(error, result);
        this.process();
      });
    }
  }
}

const queue = new CallbackQueue(2); // Max 2 concurrent

for (let i = 1; i <= 5; i++) {
  queue.add(
    (cb) => {
      console.log(`Task ${i} started`);
      setTimeout(() => cb(null, `Task ${i} result`), 100);
    },
    (err, result) => {
      console.log(`Task ${i} completed:`, result);
    }
  );
}

/**
 * EXAMPLE 19: Memoization with Callbacks
 */
console.log('\n--- Example 19: Async Memoization ---');

function memoizeAsync(func) {
  const cache = new Map();
  const pending = new Map();

  return function (key, callback) {
    // Return cached result
    if (cache.has(key)) {
      console.log('Cache hit for:', key);
      setTimeout(() => callback(null, cache.get(key)), 0);
      return;
    }

    // If request is in progress, queue callback
    if (pending.has(key)) {
      pending.get(key).push(callback);
      return;
    }

    // Start new request
    pending.set(key, [callback]);

    func(key, (error, result) => {
      const callbacks = pending.get(key);
      pending.delete(key);

      if (!error) {
        cache.set(key, result);
      }

      callbacks.forEach((cb) => cb(error, result));
    });
  };
}

// Simulated expensive operation
function fetchUser(id, callback) {
  console.log('Fetching user:', id);
  setTimeout(() => callback(null, { id, name: `User ${id}` }), 100);
}

const memoizedFetch = memoizeAsync(fetchUser);

// First call - fetches
memoizedFetch(1, (err, user) => console.log('Result 1:', user));

// Second call (same key) - uses cache
setTimeout(() => {
  memoizedFetch(1, (err, user) => console.log('Result 2:', user));
}, 200);

/**
 * EXAMPLE 20: Timeout Wrapper
 */
console.log('\n--- Example 20: Timeout Wrapper ---');

function withTimeout(operation, timeout) {
  return function (callback) {
    let completed = false;

    const timeoutId = setTimeout(() => {
      if (!completed) {
        completed = true;
        callback(new Error('Operation timed out'), null);
      }
    }, timeout);

    operation((error, result) => {
      if (!completed) {
        completed = true;
        clearTimeout(timeoutId);
        callback(error, result);
      }
    });
  };
}

// Slow operation
function slowOperation(callback) {
  setTimeout(() => callback(null, 'Done!'), 500);
}

// Fast operation
function fastOperation(callback) {
  setTimeout(() => callback(null, 'Quick!'), 50);
}

// With timeout
const timedSlow = withTimeout(slowOperation, 200);
const timedFast = withTimeout(fastOperation, 200);

timedSlow((error, result) => {
  console.log('Slow result:', error ? error.message : result);
});

timedFast((error, result) => {
  console.log('Fast result:', error ? error.message : result);
});

console.log('\n========================================');
console.log('End of Callback Examples');
console.log('========================================');
Examples - JavaScript Tutorial | DeepML