Async Events

What are Async Events?

Asynchronous events allow JavaScript to perform non-blocking operations. The event loop manages the execution of async code, enabling responsive applications.

Try Async Events

Code Editor
Console Output
Click "Run" to execute your code...

Example 1: Event Loop Basics

// JavaScript Event Loop
console.log('1: Start');

setTimeout(() => {
  console.log('2: Timeout');
}, 0);

Promise.resolve().then(() => {
  console.log('3: Promise');
});

console.log('4: End');

// Output:
// 1: Start
// 4: End
// 3: Promise
// 2: Timeout

// Explanation:
// 1. Synchronous code runs first (1, 4)
// 2. Microtasks (Promises) run next (3)
// 3. Macrotasks (setTimeout) run last (2)

// Call Stack -> Microtask Queue -> Macrotask Queue

// Detailed example
console.log('Script start');

setTimeout(() => {
  console.log('setTimeout 1');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('Promise 2');
  });

setTimeout(() => {
  console.log('setTimeout 2');
  Promise.resolve().then(() => {
    console.log('Promise 3');
  });
}, 0);

console.log('Script end');

// Output:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout 1
// setTimeout 2
// Promise 3

Example 2: Async/Await with Events

// Async function with events
async function fetchData() {
  console.log('Fetching data...');
  
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  
  return data;
}

// Using async/await
async function main() {
  try {
    const data = await fetchData();
    console.log('Data received:', data);
  } catch (error) {
    console.error('Error:', error);
  }
}

main();

// Event listener with async handler
button.addEventListener('click', async (event) => {
  console.log('Button clicked');
  
  try {
    const result = await someAsyncOperation();
    console.log('Result:', result);
  } catch (error) {
    console.error('Error:', error);
  }
});

// Multiple async operations
async function processMultiple() {
  // Sequential (one after another)
  const result1 = await operation1();
  const result2 = await operation2();
  const result3 = await operation3();
  
  console.log(result1, result2, result3);
}

// Parallel (all at once)
async function processParallel() {
  const [result1, result2, result3] = await Promise.all([
    operation1(),
    operation2(),
    operation3()
  ]);
  
  console.log(result1, result2, result3);
}

// Race condition
async function firstToFinish() {
  const result = await Promise.race([
    operation1(),
    operation2(),
    operation3()
  ]);
  
  console.log('First result:', result);
}

Example 3: setTimeout and setInterval

// setTimeout - executes once after delay
const timeoutId = setTimeout(() => {
  console.log('Executed after 2 seconds');
}, 2000);

// Clear timeout
clearTimeout(timeoutId);

// setTimeout with arguments
setTimeout((name, age) => {
  console.log(`${name} is ${age} years old`);
}, 1000, 'John', 30);

// setInterval - executes repeatedly
const intervalId = setInterval(() => {
  console.log('Executed every second');
}, 1000);

// Clear interval
clearInterval(intervalId);

// Auto-clearing interval
let count = 0;
const countInterval = setInterval(() => {
  count++;
  console.log(`Count: ${count}`);
  
  if (count === 5) {
    clearInterval(countInterval);
    console.log('Stopped!');
  }
}, 1000);

// Recursive setTimeout (better than setInterval)
function repeatTask() {
  console.log('Task executed');
  
  setTimeout(repeatTask, 1000);
}

setTimeout(repeatTask, 1000);

// Why recursive setTimeout is better:
// - Guarantees delay between executions
// - Can adjust delay dynamically
// - Won't queue up if execution takes longer than interval

// Debounce with setTimeout
function debounce(func, delay) {
  let timeoutId;
  
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// Usage: Search as user types
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((value) => {
  console.log('Searching for:', value);
  // API call here
}, 500);

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

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

// Usage: Scroll event
const throttledScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 200);

window.addEventListener('scroll', throttledScroll);

Example 4: Request Animation Frame

// requestAnimationFrame - synced with browser refresh (60fps)
function animate() {
  // Update animation
  console.log('Frame updated');
  
  // Request next frame
  requestAnimationFrame(animate);
}

// Start animation
const animationId = requestAnimationFrame(animate);

// Stop animation
cancelAnimationFrame(animationId);

// Smooth animation example
const box = document.getElementById('box');
let position = 0;

function moveBox() {
  position += 2;
  box.style.left = position + 'px';
  
  if (position < 500) {
    requestAnimationFrame(moveBox);
  }
}

requestAnimationFrame(moveBox);

// Performance monitoring
let lastTime = performance.now();
let frames = 0;

function measureFPS() {
  frames++;
  const currentTime = performance.now();
  const elapsed = currentTime - lastTime;
  
  if (elapsed >= 1000) {
    const fps = Math.round(frames / (elapsed / 1000));
    console.log(`FPS: ${fps}`);
    frames = 0;
    lastTime = currentTime;
  }
  
  requestAnimationFrame(measureFPS);
}

requestAnimationFrame(measureFPS);

// Smooth scroll animation
function smoothScrollTo(targetY, duration) {
  const startY = window.scrollY;
  const distance = targetY - startY;
  const startTime = performance.now();
  
  function scroll(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    // Easing function
    const ease = progress < 0.5
      ? 2 * progress * progress
      : 1 - Math.pow(-2 * progress + 2, 2) / 2;
    
    window.scrollTo(0, startY + distance * ease);
    
    if (progress < 1) {
      requestAnimationFrame(scroll);
    }
  }
  
  requestAnimationFrame(scroll);
}

// Usage
smoothScrollTo(1000, 1000); // Scroll to 1000px in 1 second

Example 5: Async Patterns

// Pattern 1: Loading states
async function loadData() {
  const loadingEl = document.getElementById('loading');
  const contentEl = document.getElementById('content');
  const errorEl = document.getElementById('error');
  
  try {
    loadingEl.style.display = 'block';
    errorEl.style.display = 'none';
    
    const data = await fetch('/api/data').then(r => r.json());
    
    contentEl.innerHTML = renderData(data);
    contentEl.style.display = 'block';
  } catch (error) {
    errorEl.textContent = error.message;
    errorEl.style.display = 'block';
  } finally {
    loadingEl.style.display = 'none';
  }
}

// Pattern 2: Retry logic
async function fetchWithRetry(url, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (response.ok) {
        return await response.json();
      }
    } catch (error) {
      if (i === retries - 1) throw error;
      
      console.log(`Retry ${i + 1}/${retries} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
      delay *= 2; // Exponential backoff
    }
  }
}

// Pattern 3: Timeout for async operations
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Timeout')), ms);
  });
  
  return Promise.race([promise, timeout]);
}

// Usage
try {
  const data = await withTimeout(
    fetch('/api/slow-endpoint'),
    5000
  );
} catch (error) {
  console.error('Request timed out or failed');
}

// Pattern 4: Queue for async tasks
class AsyncQueue {
  constructor(concurrency = 1) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }
  
  async add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.run();
    });
  }
  
  async run() {
    if (this.running >= this.concurrency || this.queue.length === 0) {
      return;
    }
    
    this.running++;
    const { task, resolve, reject } = this.queue.shift();
    
    try {
      const result = await task();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.running--;
      this.run();
    }
  }
}

// Usage
const queue = new AsyncQueue(2); // Max 2 concurrent tasks

for (let i = 0; i < 10; i++) {
  queue.add(async () => {
    console.log(`Task ${i} started`);
    await new Promise(r => setTimeout(r, 1000));
    console.log(`Task ${i} completed`);
    return i;
  });
}

Key Points

  • Event loop manages asynchronous code execution
  • Microtasks (Promises) execute before macrotasks (setTimeout)
  • async/await makes asynchronous code look synchronous
  • Use requestAnimationFrame for smooth animations
  • Debounce delays execution until after events stop
  • Throttle limits execution frequency
  • Always handle errors in async code with try/catch