🚧 Troubleshooting Tuesday: Los 5 Errores Async/Await Que Más Quebraderos Causan

:construction: Troubleshooting Tuesday: Debugging Async/Await - Los 5 Errores Más Comunes

Los martes nos enfocamos en resolver problemas reales. Hoy analizamos los errores más frecuentes con async/await en JavaScript y cómo solucionarlos efectivamente.

:bug: Error #1: Promise Hell (El Nuevo Callback Hell)

:cross_mark: Problema:

async function fetchUserData() {
    try {
        const user = await fetch('/api/user');
        const userData = await user.json();
        
        if (userData.hasProfile) {
            const profile = await fetch(`/api/profile/${userData.id}`);
            const profileData = await profile.json();
            
            if (profileData.hasPreferences) {
                const prefs = await fetch(`/api/preferences/${userData.id}`);
                const prefsData = await prefs.json();
                return { userData, profileData, prefsData };
            }
        }
    } catch (error) {
        console.log("Error en algún lugar...");
    }
}

:warning: Síntomas:

  • Código difícil de leer y mantener
  • Error handling poco específico
  • Requests secuenciales innecesarios

:white_check_mark: Solución:

async function fetchUserData() {
    try {
        // Paso 1: Obtener usuario
        const userResponse = await fetch('/api/user');
        if (!userResponse.ok) {
            throw new Error(`Error fetching user: ${userResponse.status}`);
        }
        const userData = await userResponse.json();
        
        // Paso 2: Preparar requests paralelos
        const requests = [Promise.resolve(userData)];
        
        if (userData.hasProfile) {
            requests.push(fetchProfile(userData.id));
        }
        
        if (userData.hasProfile) { // Solo si tiene profile
            requests.push(fetchPreferences(userData.id));
        }
        
        // Paso 3: Ejecutar en paralelo
        const [user, profile, preferences] = await Promise.all(requests);
        
        return { user, profile, preferences };
        
    } catch (error) {
        console.error('Error específico en fetchUserData:', error.message);
        throw new Error(`Failed to fetch user data: ${error.message}`);
    }
}

async function fetchProfile(userId) {
    const response = await fetch(`/api/profile/${userId}`);
    if (!response.ok) throw new Error(`Profile fetch failed: ${response.status}`);
    return response.json();
}

async function fetchPreferences(userId) {
    const response = await fetch(`/api/preferences/${userId}`);
    if (!response.ok) throw new Error(`Preferences fetch failed: ${response.status}`);
    return response.json();
}

:alarm_clock: Error #2: No Manejar Timeouts

:cross_mark: Problema:

// API lenta = aplicación colgada
const data = await fetch('/api/slow-endpoint');

:white_check_mark: Solución con AbortController:

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    
    // Timeout automático
    const timeoutId = setTimeout(() => {
        controller.abort();
    }, timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        return await response.json();
        
    } catch (error) {
        clearTimeout(timeoutId);
        
        if (error.name === 'AbortError') {
            throw new Error(`Request timeout after ${timeout}ms`);
        }
        
        throw error;
    }
}

// Uso
try {
    const data = await fetchWithTimeout('/api/slow-endpoint', 3000);
} catch (error) {
    console.error('API call failed:', error.message);
}

:counterclockwise_arrows_button: Error #3: Loops Async Incorrectos

:cross_mark: Problema - forEach con async:

// ¡No funciona como esperas!
const users = ['1', '2', '3'];
users.forEach(async (id) => {
    const user = await fetchUser(id);
    console.log(user); // Se ejecutan todas al mismo tiempo
});

:white_check_mark: Soluciones según el caso:

Secuencial (uno después del otro):

const users = [];
for (const id of ['1', '2', '3']) {
    const user = await fetchUser(id);
    users.push(user);
    console.log(`Procesado usuario ${id}`);
}

// O con reduce para más control
const users = await ['1', '2', '3'].reduce(async (prevPromise, id) => {
    const acc = await prevPromise;
    const user = await fetchUser(id);
    acc.push(user);
    return acc;
}, Promise.resolve([]));

Paralelo (todos al mismo tiempo):

const users = await Promise.all(
    ['1', '2', '3'].map(id => fetchUser(id))
);

// Con manejo de errores individual
const users = await Promise.allSettled(
    ['1', '2', '3'].map(async (id) => {
        try {
            return await fetchUser(id);
        } catch (error) {
            console.error(`Error fetching user ${id}:`, error);
            return null;
        }
    })
);

const successfulUsers = users
    .filter(result => result.status === 'fulfilled')
    .map(result => result.value);

:collision: Error #4: No Propagar Errores Correctamente

:cross_mark: Problema:

async function saveUser(userData) {
    try {
        await api.saveUser(userData);
        console.log("Usuario guardado"); // ¿Pero se guardó realmente?
    } catch (error) {
        console.log("Error guardando usuario");
        // ¡Error se pierde aquí!
    }
}

// En el componente
await saveUser(data); // No sabes si funcionó

:white_check_mark: Solución:

async function saveUser(userData) {
    try {
        const result = await api.saveUser(userData);
        console.log("✅ Usuario guardado exitosamente:", result.id);
        return { success: true, data: result };
        
    } catch (error) {
        console.error("❌ Error guardando usuario:", error.message);
        
        // Re-throw con contexto adicional
        throw new Error(`Failed to save user: ${error.message}`);
    }
}

// En el componente
try {
    const result = await saveUser(data);
    showSuccessMessage("Usuario guardado correctamente");
} catch (error) {
    showErrorMessage(error.message);
    // Opcional: reportar error a servicio de logging
    errorLogger.report(error);
}

:shuffle_tracks_button: Error #5: Race Conditions en Estado

:cross_mark: Problema:

let isLoading = false;

async function loadData() {
    if (isLoading) return; // Race condition aquí
    
    isLoading = true;
    const data = await fetchData();
    isLoading = false;
    
    updateUI(data);
}

:white_check_mark: Solución con Request ID:

class DataLoader {
    constructor() {
        this.currentRequestId = 0;
        this.isLoading = false;
    }
    
    async loadData() {
        // Generar ID único para esta request
        const requestId = ++this.currentRequestId;
        
        if (this.isLoading) {
            console.log('Request already in progress, ignoring');
            return;
        }
        
        this.isLoading = true;
        
        try {
            const data = await fetchData();
            
            // Verificar si somos la request más reciente
            if (requestId === this.currentRequestId) {
                updateUI(data);
            } else {
                console.log('Discarding stale response');
            }
            
        } finally {
            this.isLoading = false;
        }
    }
}

const loader = new DataLoader();

// Múltiples llamadas rápidas
loader.loadData(); // Se ejecuta
loader.loadData(); // Se ignora
loader.loadData(); // Se ignora

:hammer_and_wrench: Herramientas de Debugging Async

Wrapper para logging automático:

function asyncLogger(fn, name) {
    return async function(...args) {
        console.log(`🚀 Iniciando ${name}`, args);
        const startTime = performance.now();
        
        try {
            const result = await fn.apply(this, args);
            const duration = performance.now() - startTime;
            console.log(`✅ ${name} completado en ${duration.toFixed(2)}ms`, result);
            return result;
        } catch (error) {
            const duration = performance.now() - startTime;
            console.error(`❌ ${name} falló después de ${duration.toFixed(2)}ms:`, error);
            throw error;
        }
    };
}

// Uso
const fetchUser = asyncLogger(async (id) => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}, 'fetchUser');

:light_bulb: Quick Fix Checklist

Cuando debuggees código async, verifica:

  • :white_check_mark: Error handling específico en cada nivel
  • :white_check_mark: Timeouts para evitar requests colgados
  • :white_check_mark: Requests paralelos cuando sea posible
  • :white_check_mark: Race condition protection en estado compartido
  • :white_check_mark: Logging con contexto y timing
  • :white_check_mark: Response validation antes de usar datos

:speech_balloon: ¿Qué error async les ha dado más dolores de cabeza? ¿Han encontrado otras soluciones creativas? Compartamos experiencias de debugging para aprender juntos.

#TroubleshootingTuesday asyncawait javascript debugging #ErrorHandling webdev