Programación asíncrona en TypeScript: guía completa en la práctica
El desarrollo web moderno es impensable sin operaciones asíncronas: solicitudes a API, trabajo con bases de datos, lectura de archivos y temporizadores. TypeScript, como superconjunto de JavaScript, no solo conserva todas las capacidades de programación asíncrona, sino que también añade tipado estricto, haciendo el código más predecible y seguro. En esta guía analizaremos los conceptos clave de asincronía en TypeScript — desde los Promise básicos hasta patrones avanzados — con ejemplos de código reales.
1. Fundamentos: Promise y tipado
Promise es un objeto que representa el resultado de una operación asíncrona, que puede completarse exitosamente (fulfilled) o ser rechazada (rejected). TypeScript permite tipar estrictamente qué valor devolverá el Promise.
// Ejemplo de Promise tipadofunction fetchUserData(userId: number): Promise<{ name: string; email: string }> { return new Promise((resolve, reject) => { setTimeout(() => { if (userId > 0) { resolve({ name: 'Iván Ivanov', email: 'ivan@example.com' }); } else { reject(new Error('ID de usuario no válido')); } }, 1000); });}
// UsofetchUserData(1) .then(user => console.log(user.name)) // Iván Ivanov .catch(error => console.error(error.message));Observa: Promise<{ name: string; email: string }> garantiza que en then obtendremos un objeto exactamente con estos campos. Esto evita errores relacionados con propiedades indefinidas.
2. Async/Await: azúcar sintáctico con TypeScript
Las palabras clave async y await hacen que el código asíncrono se parezca al síncrono, sin perder rendimiento. TypeScript determina automáticamente el tipo de retorno de la función async como Promise<T>.
// La función devuelve Promise<number>async function getDiscountPrice(price: number, discountPercent: number): Promise<number> { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Porcentaje de descuento no válido'); } // Simulación de cálculo asíncrono const discount = await calculateDiscountAsync(price, discountPercent); return price - discount;}
// Función auxiliarfunction calculateDiscountAsync(price: number, percent: number): Promise<number> { return Promise.resolve(price * percent / 100);}
// Usoasync function main() { try { const finalPrice = await getDiscountPrice(1000, 20); console.log(`Precio final: ${finalPrice}`); // 800 } catch (error) { if (error instanceof Error) { console.error('Error:', error.message); } }}main();Importante: TypeScript no permite usar await fuera de una función async (excepto en el nivel superior en módulos con configuraciones específicas). Siempre envuelve las llamadas en una función async o usa .then().
3. Manejo de errores y patrones avanzados
Un manejo adecuado de errores es la base de un código asíncrono robusto. TypeScript ayuda a detectar errores en tiempo de compilación, pero la lógica de manejo debe escribirse manualmente.
3.1. Manejo de errores en async/await
async function safeFetchData(url: string): Promise<unknown> { try { const response = await fetch(url); if (!response.ok) { throw new Error(`¡Error HTTP! Estado: ${response.status}`); } return await response.json(); } catch (error) { // TypeScript reduce el tipo a Error if (error instanceof Error) { console.error('Error de solicitud:', error.message); // Se puede devolver un valor por defecto o relanzar el error return null; } throw error; // Error desconocido