TechLead
🔄
Intermedio
10 min lectura

Event Loop y rendimiento asíncrono

Optimiza operaciones asíncronas y comprende el event loop

Comprendiendo el Event Loop de JavaScript

El event loop es el mecanismo que permite a JavaScript manejar operaciones asíncronas pese a ser single‑thread. Entender cómo funciona es clave para escribir código rápido.

Fases del Event Loop

  • Call Stack: Ejecuta código síncrono
  • Cola de microtareas: Promesas, queueMicrotask, MutationObserver
  • Cola de macrotareas: setTimeout, setInterval, operaciones de I/O
  • Cola de render: requestAnimationFrame, render del navegador

Microtareas vs macrotareas

Comprender la diferencia entre microtareas y macrotareas es esencial para optimizar:

// ❌ Bad: Blocking the event loop with long synchronous operations
function processLargeArray(arr) {
  arr.forEach(item => {
    // Complex synchronous operation
    complexCalculation(item);
  });
}

processLargeArray(hugeArray); // Blocks UI for too long

// ✅ Good: Break work into chunks using microtasks
async function processLargeArrayOptimized(arr) {
  const chunkSize = 100;
  for (let i = 0; i < arr.length; i += chunkSize) {
    const chunk = arr.slice(i, i + chunkSize);
    chunk.forEach(item => complexCalculation(item));
    
    // Allow other tasks to run
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

// ✅ Even better: Use requestIdleCallback for non-critical work
function processWithIdleCallback(arr) {
  let index = 0;
  
  function processChunk(deadline) {
    while (deadline.timeRemaining() > 0 && index < arr.length) {
      complexCalculation(arr[index++]);
    }
    
    if (index < arr.length) {
      requestIdleCallback(processChunk);
    }
  }
  
  requestIdleCallback(processChunk);
}

Optimización de Promesas

Optimiza cadenas de Promises y operaciones async:

// ❌ Bad: Sequential Promise execution
async function fetchAllData() {
  const user = await fetchUser();
  const posts = await fetchPosts();
  const comments = await fetchComments();
  return { user, posts, comments };
}

// ✅ Good: Parallel Promise execution
async function fetchAllDataOptimized() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ]);
  return { user, posts, comments };
}

// ✅ Good: Handle partial failures gracefully
async function fetchWithFallback() {
  const results = await Promise.allSettled([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ]);
  
  return results.map(result => 
    result.status === 'fulfilled' ? result.value : null
  );
}

Evitar bloquear el event loop

// ❌ Bad: Long-running synchronous loop
function calculateFibonacci(n) {
  if (n <= 1) return n;
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
}

// ✅ Good: Break into smaller tasks
async function calculateFibonacciOptimized(n, memo = {}) {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];
  
  // Yield to event loop periodically
  if (n % 10 === 0) {
    await new Promise(resolve => setTimeout(resolve, 0));
  }
  
  memo[n] = await calculateFibonacciOptimized(n - 1, memo) + 
             await calculateFibonacciOptimized(n - 2, memo);
  return memo[n];
}

// ✅ Best: Use Web Workers for CPU-intensive tasks
const worker = new Worker('fibonacci-worker.js');
worker.postMessage({ n: 40 });
worker.onmessage = (e) => console.log('Result:', e.data);

Planificación de microtareas

// Different ways to schedule tasks
console.log('1: Synchronous');

setTimeout(() => console.log('2: Macrotask (setTimeout)'), 0);

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

queueMicrotask(() => console.log('4: Microtask (queueMicrotask)'));

console.log('5: Synchronous');

// Output order:
// 1: Synchronous
// 5: Synchronous
// 3: Microtask (Promise)
// 4: Microtask (queueMicrotask)
// 2: Macrotask (setTimeout)

Buenas prácticas

  • Usa Promise.all() para operaciones async en paralelo
  • Divide tareas largas en chunks con setTimeout o requestIdleCallback
  • Prioriza tareas críticas con microtareas (Promises)
  • Difiere trabajo no crítico con macrotareas (setTimeout)
  • Usa Web Workers para cálculos intensivos de CPU
  • Evita cadenas de Promises profundamente anidadas: usa async/await
  • Monitorea el lag del event loop con performance.now()
  • Usa Promise.race() para patrones de timeout