Programación asíncrona en TypeScript: guía completa para principiantes
El desarrollo web moderno es imposible sin comprender la programación asíncrona. TypeScript, como extensión de JavaScript, proporciona herramientas potentes para trabajar con código asíncrono, haciéndolo más seguro y predecible. En esta guía, analizaremos los conceptos clave de la programación asíncrona en TypeScript, comenzando desde lo básico hasta ejemplos prácticos.
¿Qué es la programación asíncrona y por qué es necesaria?
En la programación síncrona, el código se ejecuta estrictamente de forma secuencial: cada operación siguiente espera a que finalice la anterior. Esto crea problemas al trabajar con tareas largas, como solicitudes al servidor, lectura de archivos o temporizadores. La programación asíncrona permite ejecutar estas tareas en segundo plano sin bloquear el flujo principal de ejecución.
TypeScript añade al JavaScript asíncrono un tipado estricto, lo que ayuda a detectar errores en la etapa de compilación. Por ejemplo, las Promise tipadas permiten saber exactamente qué tipo de datos devolverá una función asíncrona.
// Código síncrono - bloquea la ejecución
function fetchData(): string {
// Operación larga
return "datos";
}
// Código asíncrono - no bloquea
async function fetchDataAsync(): Promise<string> {
const response = await fetch('/api/data');
return response.json();
}
Fundamentos: Callback, Promise y Async/Await
Históricamente, la asincronía en JavaScript comenzó con funciones callback, que se pasaban como argumentos. Sin embargo, este enfoque conducía al "infierno de los callbacks": estructuras profundamente anidadas, difíciles de entender y depurar.
Promises (promesas) resolvieron este problema proporcionando una sintaxis encadenada. Una Promise es un objeto que representa el resultado de una operación asíncrona, que puede estar en uno de tres estados: pending (pendiente), fulfilled (cumplida) o rejected (rechazada).
// Ejemplo de Promise en TypeScript
function loadUser(id: number): Promise<User> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Alice" });
} else {
reject(new Error("ID de usuario no válido"));
}
}, 1000);
});
}
// Uso
loadUser(1)
.then(user => console.log(user))
.catch(error => console.error(error));
Async/Await es azúcar sintáctico sobre las Promises, que hace que el código asíncrono se parezca al síncrono. La palabra clave async antes de una función indica que devuelve una Promise, y await pausa la ejecución hasta obtener el resultado.
// Async/Await en TypeScript
async function getUserData(): Promise<void> {
try {
const user = await loadUser(1);
console.log(user.name); // "Alice"
} catch (error) {
console.error("Error de carga:", error);
}
}
getUserData();
Tipado de funciones asíncronas en TypeScript
Una de las principales ventajas de TypeScript es el tipado estricto. Para las funciones asíncronas, es importante indicar correctamente el tipo de Promise que devuelven. Si una función devuelve una cadena, el tipo será Promise<string>. El compilador de TypeScript verificará que no intentes usar el resultado antes de obtenerlo.
Consideremos un ejemplo con una solicitud API tipada:
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
async function fetchTodo(id: number): Promise<Todo> {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
if (!response.ok) {
throw new Error(`¡Error HTTP! estado: ${response.status}`);
}
return await response.json() as Todo;
}
// Uso con tipado
async function displayTodo(): Promise<void> {
const todo = await fetchTodo
```