Introducción a la programación asíncrona en JavaScript
\n\nJavaScript es un lenguaje de programación de un solo hilo. Esto significa que en cada momento solo se ejecuta una operación. Sin embargo, si todas las operaciones se ejecutaran secuencialmente, el navegador se "congelaría" al cargar datos del servidor o leer un archivo. Aquí es donde la programación asíncrona viene al rescate.
\n\nLa programación asíncrona permite ejecutar operaciones largas (solicitudes a API, lectura de archivos, temporizadores) sin bloquear el hilo principal de ejecución. En lugar de esperar a que finalice una tarea, el código continúa ejecutándose y el resultado se procesa más tarde. Este es un concepto clave para crear aplicaciones rápidas y receptivas en Node.js.
\n\nEn esta guía, analizaremos tres enfoques principales para la programación asíncrona: callbacks, promesas y la sintaxis async/await. Aprenderás a trabajar correctamente con código asíncrono y evitar los errores típicos de los principiantes.
1. Callbacks: la base de la asincronía
\n\nUn callback es una función que se pasa como argumento a otra función y se llama después de que se completa una operación asíncrona. Es la forma más antigua y básica de trabajar con asincronía en JavaScript.
\n\nEjemplo con setTimeout
\n\nLa función incorporada setTimeout es un ejemplo clásico de operación asíncrona. Toma un callback y un retardo en milisegundos:
console.log('Inicio');\n\nsetTimeout(() => {\n console.log('Pasaron 2 segundos');\n}, 2000);\n\nconsole.log('Fin');\n\n// Salida:\n// Inicio\n// Fin\n// Pasaron 2 segundos\n\nObserva que "Fin" se muestra antes que el mensaje del callback. Esto es la asincronía: el código no espera a que setTimeout termine.
El problema del "Callback Hell"
\n\nCuando tienes muchas operaciones asíncronas anidadas, el código se vuelve difícil de leer. Este fenómeno se llama "infierno de callbacks" (Callback Hell):
\n\nfs.readFile('archivo1.txt', (err, data1) => {\n if (err) throw err;\n fs.readFile('archivo2.txt', (err, data2) => {\n if (err) throw err;\n fs.readFile('archivo3.txt', (err, data3) => {\n if (err) throw err;\n console.log(data1, data2, data3);\n });\n });\n});\n\nEste código es difícil de mantener y depurar. Afortunadamente, JavaScript moderno ofrece soluciones más elegantes.
\n\n2. Promesas: manejo elegante de la asincronía
\n\nUna Promise (promesa) es un objeto que representa el resultado de una operación asíncrona. Una promesa puede estar en uno de tres estados:
\n\n- \n
- Pending (pendiente) — estado inicial, la operación aún no ha terminado \n
- Fulfilled (cumplida) — la operación se completó con éxito \n
- Rejected (rechazada) — la operación se completó con un error \n
Creación y uso de una Promise
\n\nconst miPromesa = new Promise((resolve, reject) => {\n setTimeout(() => {\n const exito = true;\n if (exito) {\n resolve('Datos obtenidos');\n } else {\n reject('Error al obtener datos');\n }\n }, 2000);\n});\n\nmiPromesa\n .then(resultado => {\n console.log(resultado); // "Datos obtenidos"\n })\n .catch(error => {\n console.error(error); // "Error al obtener datos"\n })\n .finally(() => {\n console.log('Operación completada');\n });\n\nEncadenamiento de promesas (Chaining)
\n\nLas promesas se pueden encadenar, lo que permite evitar el "infierno de callbacks":
\n\nfunction leerArchivo(nombreArchivo) {\n return new Promise((resolve, reject) => {\n fs.readFile(nombreArchivo, 'utf8', (err, data) => {\n if (err) reject(err);\n else resolve(data);\n });\n });\n}\n\nleerArchivo('archivo1.txt')\n .then(data1 => {\