Artículo original: Asynchronous Programming in JavaScript – Guide for Beginners
Para comprender lo que significa la programación asíncrona, te propongo que imagines a mucha gente trabajando en simultáneo en un mismo proyecto, cada cual en una tarea distinta.
Con la programación tradicional (y sincrónica), cada persona debería esperar a que la persona anterior termine su tarea antes de comenzar la propia.
Con la programación asíncrona, por el contrario, cada uno puede comenzar su tarea y trabajar en paralelo sin tener que esperar a que las otras personas finalicen su trabajo.
De forma similar, en un programa informático, la programación asíncrona permite al programa trabajar en distintas tareas simultáneamente sin tener que esperar a completar una tarea para pasar a la siguiente. Esto permite que un programa sea capaz de hacer más cosas en menos tiempo.
Por ejemplo: un programa puede enviar una solicitud a un servidor mientras gestiona y procesa la información del usuario, todo al mismo tiempo. De esta forma, un programa puede ejecutarse de forma mucho más eficiente.

En este artículo, nos adentraremos en el mundo de la programación asíncrona en JavaScript, explorando las diversas técnicas y conceptos necesarios para utilizar este poderoso paradigma de programación.
Desde las callbacks a las promesas y las async/await, comprenderás cómo aprovechar la programación asíncrona en tus proyectos de JavaScript.
Entender la programación asíncrona es esencial para construir aplicaciones web de alto rendimiento, tanto si eres un desarrollador experimentado como si estás dando tus primeros pasos con JavaScript. Por ello, te invito a seguir leyendo para saber más sobre este concepto tan importante.
¿Qué es la Programación Sincrónica?
La programación sincrónica es una técnica que se utiliza para que las computadoras realicen tareas paso a paso, en el orden en que se les dan las instrucciones.
Imagina que estás cocinando la cena y tienes una lista de tareas, como hervir el agua para la pasta, freír el pollo y preparar una ensalada.

¿Harías estas tareas una por una y esperarías a que cada una terminara antes de pasar a la siguiente?
La programación sincrónica funciona de manera similar: la computadora completará cada tarea antes de pasar a la siguiente. Esto hace que sea fácil de entender y de predecir lo que la computadora hará en cualquier momento dado.
Aquí un ejemplo de código sincrónico en JavaScript:
// Define tres funciones
function primeraTarea() {
console.log("Tarea 1");
}
function segundaTarea() {
console.log("Tarea 2");
}
function terceraTarea() {
console.log("Tarea 3");
}
// Ejecuta las funciones
primeraTarea();
segundaTarea();
terceraTarea();
Este código mostrará los siguientes mensajes en el orden en que aparecen:
"Tarea 1"
"Tarea 2"
"Tarea 3"
El código ejecutará las tareas en el orden en que las ves y esperará a que cada tarea se complete antes de pasar a la siguiente.

Sin embargo, la programación sincrónica puede ser problemática en ciertas situaciones, especialmente cuando se trata de tareas que requieren una cantidad significativa de tiempo para completarse.
Por ejemplo, supongamos que un programa sincrónico realiza una tarea que requiere esperar una respuesta de un servidor remoto. El programa quedará a la espera de la respuesta y no podrá hacer nada más hasta que se devuelva la respuesta. Esto se conoce como bloqueo y puede hacer que una aplicación no responda o aparezca como "congelada" para el usuario.
Examinemos el siguiente código:
function funcionDeLargaDuracion() {
let inicio = Date.now();
while (Date.now() - inicio < 5000) {
// no hacer nada
}
return "Hola";
}
console.log('Iniciando...');
let resultado = funcionDeLargaDuracion();
console.log(resultado);
console.log('...Finalizando');
En este ejemplo:
- El programa comienza registrando "Iniciando..." en la consola.
- Luego llama a la función
funcionDeLargaDuracion
, que simula una tarea que tarda 5 segundos en completarse. Esta función bloqueará la ejecución del resto del programa mientras se ejecuta. - Una vez que la función se complete, devolverá "Hola", y el programa lo imprimirá en la consola.
- Finalmente, el programa registrará "Finalizando..." en la consola.
Durante los 5 segundos que la funcionDeLargaDuracion()
se está ejecutando, el programa quedará bloqueado y no podrá ejecutar la siguiente línea de código. Esto puede hacer que el programa tarde mucho en completarse y la aplicación quede congelada y sin respuesta para el usuario.
Por el contrario, si el programa se ejecutara de manera asíncrona, este podría seguir ejecutando las instrucciones de la siguiente línea de código en lugar de bloquearse. Esto permitiría que el programa siga siendo receptivo y ejecute otras instrucciones de código mientras espera a que se complete el tiempo de espera.
¿Qué es la Programación Asincrónica?
La programación asíncrona es una forma en que un programa de computadora gestiona múltiples tareas simultáneamente en lugar de ejecutarlas una tras otra.

La programación asíncrona permite que un programa continúe trabajando en otras tareas mientras espera que ocurran eventos externos, como solicitudes de red. Este enfoque puede mejorar enormemente el rendimiento y la capacidad de respuesta de un programa.
Por ejemplo, mientras un programa recupera los datos de un servidor remoto, puede seguir ejecutando otras tareas como responder a las entradas del usuario.
Aquí, un ejemplo de un programa asíncrono que utiliza el método
setTimeout
:
console.log("Inicio del guión");
setTimeout(function() {
console.log("Primer tiempo muerto completado");
}, 2000);
console.log("Fin del guión");
En este ejemplo, el método setTimeout
ejecuta una función después de un tiempo especificado. La función que se le pasa a setTimeout
se ejecutará de forma asíncrona, lo que significa que el programa seguirá ejecutando la siguiente línea de código sin esperar a que se complete el tiempo de espera.
Cuando se ejecuta el código, obtendrás lo siguiente:
Inicio del guión
Fin del guión
Primer tiempo muerto completado
Como puedes ver, console.log("Primer tiempo muerto completado")
se ejecutará después de 2 segundos. Mientras tanto, el guion continúa ejecutando la siguiente línea de código sin causar ningún bloqueo o "congelamiento".
En JavaScript, la programación asíncrona se puede utilizar a través de distintas técnicas. Uno de los métodos más comunes es el uso de las callbacks o retrollamadas.
¿Cómo utilizar las funciones Callback?
Imagina que quieres planear una fiesta de cumpleaños para tu hijo/a. Tienes que enviar las invitaciones, encargar una torta y planear los juegos. Pero también quieres contratar a un payaso para entretener a los invitados. Solo puedes hacer que el payaso venga a la fiesta una vez que se hayan realizado todos los demás preparativos y los invitados hayan llegado.

Entonces, le dices al payaso que venga a la fiesta solo después de que le hayas notificado que los invitados han llegado. En este caso, el payaso representa una función callback, y la "llegada de los invitados" representa la función cuya ejecución se debe completar antes de que se pueda ejecutar la devolución de llamada.
En el código, una callback es una función que se pasa como argumento a otra función y se ejecuta después de que se haya completado la ejecución de la primera función. Se usa comúnmente en JavaScript para manejar operaciones asíncronas como obtener datos de un servidor, esperar el ingreso de datos por parte del usuario o manejar eventos.
Aquí, un ejemplo simple de cómo puede usar una función callback para manejar una operación asíncrona:
// Declarar función
function obtenerDatos(callback) {
setTimeout(() => {
const datos = {nombre: "John", edad: 30};
callback(datos);
}, 3000);
}
// Ejecutar función con una callback
obtenerDatos(function(datos) {
console.log(datos);
});
console.log("Se están obteniendo los datos...");
En este ejemplo:
- Tenemos una función llamada
fetchData
que usa el métodosetTimeout
para simular una operación asíncrona. La función recibe una callback como argumento. - La función callback recibe los datos recuperados por la función después de que se haya completado el tiempo de espera.
El método setTimeout
se usa para ejecutar la callback después de un tiempo especificado (en este caso, 3 segundos). La callback se ejecutará de forma asíncrona, lo que significa que el programa ejecutará la siguiente línea de código sin esperar a que se complete el tiempo de espera.
Cuando se ejecuta el código, el resultado será:
Se están obteniendo los datos...
{nombre: "John", edad: 30}
Como se puede observar, el console.log("Primer tiempo muerto completado")
se ejecutará después de 3 segundos. Mientras tanto, el guión continúa ejecutando la siguiente instrucción, console.log("Se están obteniendo los datos...");
.
Este es el concepto principal de la programación asíncrona. El guión no espera a que se complete la operación asíncrona. Simplemente continúa ejecutando la siguiente instrucción.
¿Qué es el Callback Hell o infierno de Callbacks?
Los callbacks proveen una forma útil de manejar operaciones asíncronas. Sin embargo, cuando se anidan muchos callbacks, el código se vuelve complejo y difícil de leer y de entender.
Esto ocurre cuando se encadenan múltiples callbacks uno tras otro, creando una estructura piramidal de indentación a la que se le da el nombre de callback hell (o infierno de callbacks). También se le conoce como "Pirámide infernal" o Pyramid of Doom.

Aquí, un ejemplo de callback hell:
obtenerDatos(function(a) {
obtenerMasDatos(a, function(b) {
obtenerAunMasDatos(b, function(c) {
obtenerAunMasYMasDatos(c, function(d) {
obtenerDatosFinales(d, function(datosFinales) {
console.log(datosFinales);
});
});
});
});
});
En este ejemplo:
- La función
getData
recibe un callback como argumento y se ejecuta después de que se recopilen los datos. - El callback toma los datos y llama a la función
getMoreData
, que también recibe un callback como argumento, y así sucesivamente.
Este anidamiento de callbacks puede hacer que el código sea difícil de mantener, y la indentación vuelve aún más difícil de ver la estructura general del código.
Para evitar los callback hell, se puede utilizar una forma más moderna de manejar las operaciones asíncronas : las promesas. Las promesas ofrecen una forma más elegante de manejar el flujo asíncrono de un programa en comparación con las funciones de callback. Este es el enfoque de la siguiente sección.
¿Cómo funcionan las promesas?
Una promesa representa una forma de manejar las operaciones asíncronas de una manera más organizada. Sirve para lo mismo que un callback, pero ofrece capacidades adicionales y tiene una sintaxis más legible.
Una promesa en JavaScript es un marcador de posición para un valor o acción futura. Al crear una promesa, se le está diciendo al motor de JavaScript que "prometa" realizar una acción específica y notificarte una vez que se haya completado o fallado.

Las funciones callback se adjuntan a las promesas para manejar el resultado de la acción. Estas callbacks serán llamadas cuando la promesa se cumpla (acción completada exitosamente) o cuando se rechace (acción fallida).
Como desarrollador/a de JavaScript, es probable que pases más tiempo consumiendo promesas devueltas por las API web asíncronas y gestionando sus resultados en lugar de crearlas tu mismo/a.
¿Cómo crear una Promesa?
Para crear una promesa, deberás crear una nueva instancia del objeto Promise
llamando al constructor Promise
.
El constructor toma un solo argumento: una función llamada executor
. La función "executor" es llamada inmediatamente cuando se crea la promesa, y toma dos argumentos: una función de resolución (función resolve
) y una función de rechazo (función reject
).

Escribe la siguiente línea de código para declarar una promesa:
// Inicializar una promesa
const myPromise = new Promise(function(resolve, reject) => {})
Ahora, inspeccionemos el objeto myPromise
imprimiéndolo en la consola.
console.log(myPromise);

promise
object. Como se puede ver, la promesa tiene un estado pendiente y un valor indefinido. Esto se debe a que aún no se ha realizado ninguna configuración para el objeto de la promesa, por lo que va a permanecer indefinidamente en un estado pendiente sin ningún valor o resultado.
Ahora, configuremos myPromise
para resolver con una cadena impresa en la consola después de 2 segundos.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("¡Saludos desde la promesa!");
}, 2000);
});
Ahora, al inspeccionar el objeto myPromise
, encontrarás que tiene un estado de "cumplida" y un valor que corresponderá a la cadena de caracteres que se le pasó a la función resolve
.

Una promesa tiene tres estados:
- Pendiente: es el estado inicial, ni cumplida ni rechazada.
- Cumplida: significa que la operación se completó con éxito.
- Rechazada: significa que la operación falló.

Es importante tener en cuenta que se dice que una promesa está resuelta tanto si se completa con éxito como si se rechaza.
Ahora que has visto cómo se crean las promesas, veamos cómo se hace para consumirlas.
¿Cómo consumir una Promesa?
Consumir una promesa involucra los siguientes pasos:
- Obtener una referencia a una promesa: Para consumir una promesa, primero necesitas obtener una referencia a ella. Basándonos en el código de la sección anterior, nuestra referencia a una promesa será el objeto
myPromise
. - Adjuntar callbacks a una promesa: Una vez que tienes una referencia, puedes adjuntar funciones de callback utilizando los métodos
.then
y.catch
. El método.then
se llama cuando una promesa se cumple y el método.catch
se llama cuando una promesa se rechaza. - Esperar a que la promesa se resuelva: Una vez que has adjuntado los callbacks a la promesa, puedes esperar a que la promesa se cumpla o se rechace.
Aquí, un ejemplo de cómo puedes consumir una promesa:
myPromise
.then((resultado) => {
console.log(resultado);
})
.catch((error) => {
console.log(error);
});
Una vez que se cumple la promesa, se llamará al método de callback .then
con el valor resuelto. Si la promesa se rechaza, se llamará al método .catch
con un mensaje de error.
También puedes agregar el método .finally()
, que se llamará después de que una promesa se resuelva. Esto significa que .finally()
se invocará independientemente del estado de una promesa (ya sea resuelta o rechazada).
myPromise
.then((resultado) => {
console.log(resultado);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
//aquí va el código que será ejecutado independientemente de que la //promesa se cumpla o se rechace.
});
¿Cómo encadenar Promesas?
El encadenamiento de promesas es un patrón que permite manejar operaciones asíncronas de una forma clara y fácil de entender.
El patrón implica conectar múltiples promesas en una secuencia, donde el resultado de una promesa se pasa como argumento a la siguiente promesa.
La vinculación de las promesas se realiza mediante el uso del método then()
. Este método utiliza una función de callback como argumento y devuelve una nueva promesa. La nueva promesa se resuelve con el valor devuelto por la función de callback.
Un ejemplo de encadenamiento de promesas:
fetch('https://example.com/data')
.then(response => response.json())
.then(data => processData(data))
.then(processedData => {
//haz algo con la información procesada
})
.catch(error => console.log(error))
A partir del código anterior:
- La primera promesa, que es la función
fetch API
, está recuperando datos de un servidor. - La segunda promesa está analizando la respuesta como JSON.
- La tercera promesa está procesando la información.
- La cuarta promesa está realizando una acción con la información obtenida.
- El método
.catch
al final de la cadena manejará cualquier error que ocurra en cualquiera de las promesas anteriores.
Es importante tener en cuenta que los métodos .then
se ejecutan de manera asíncrona y en orden, cada uno esperando a que el anterior se resuelva, y que el valor devuelto de cada .then
se pasa como argumento al siguiente.
Manejo de Errores
Cuando una promesa es rechazada, se activará el método .catch()
, que es el encargado de manejar los errores. El método .catch()
toma un único argumento, que es el error devuelto.
Otra forma de manejar errores en una promesa es usando el bloque "try-catch" dentro de un método .then
.
Por ejemplo:
fetch("https://api.github.com/users/octocat")
.then((response) => response.json())
.then((data) => {
try {
//procesando la información recibida
console.log(data);
} catch (error) {
console.log(error);
}
})
.catch((error) => console.log(error));
Del código anterior:
- La función
fetch()
hace una solicitud a la API de GitHub para obtener datos de usuario. - El bloque "try-catch" se utiliza dentro del segundo
.then
para manejar cualquier error que pueda ocurrir en el procesamiento de la información recibida del servidor. - Por último, el método
.catch
externo sólo manejará los errores que ocurran durante la solicitud de datos.
El manejo de errores es muy importante ya que las promesas se usan para manejar operaciones asíncronas, y estas operaciones pueden fallar por razones muy diversas.
Si se produce un error durante la ejecución de una promesa y no se maneja, el programa continuará ejecutándose y puede provocar un comportamiento inesperado o fallas.
Al manejar errores, nos aseguramos de que el programa podrá seguir funcionando incluso cuando ocurra un error y podremos brindarle al usuario información valiosa sobre el problema.
¿Cómo usar el método Promise.all?
El método Promise.all()
recibe un arreglo de promesas como argumento y retorna una única promesa que se cumple solamente cuando todas las promesas del arreglo se hayan cumplido. Esto puede ser útil cuando esperas a la resolución de muchas promesas antes de realizar una acción.
Por ejemplo, si quieres obtener información de múltiples URLs.
let promesa1 = fetch('https://jsonplaceholder.typicode.com/posts/1');
let promesa2 = fetch('https://jsonplaceholder.typicode.com/posts/2');
let promesa3 = fetch('https://jsonplaceholder.typicode.com/posts/3');
Aquí, promesa1
, promesa2
, y promesa3
son promesas que obtienen información de tres URLs distintas.
Ahora puedes utilizar Promise.all([promesa1, promesa2, promesa3])
para esperar a que se resuelvan todas las promesas antes de realizar una acción con los datos, como se muestra a continuación.
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
En el ejemplo anterior:
Promise.all()
recibe un arreglo de promesas como argumento y devuelve una nueva promesa.- El método
then
se llama en la promesa devuelta para imprimir en consola los resultados de todas las promesas que se pasaron como argumento, en el orden en que se pasaron aPromise.all()
.

Ten en cuenta que en caso de que alguna de las promesas pasadas como argumento sea rechazada, la promesa devuelta también será rechazada con el valor de la primera promesa rechazada.
¿Cómo utilizar Fetch API con Promesas?
En algunos de los ejemplos en este artículo hemos estado utilizando API Fetch que podría ser desconocida para algunos lectores. Por ello, en esta sección explicaremos los conceptos básicos de la API Fetch para aquellos que necesiten familiarizarse más con ella.
La API Fetch es una función incorporada en JavaScript que permite realizar solicitudes de red, como obtener datos de un servidor. Es una alternativa moderna a la antigua API XMLHttpRequest y está diseñada para ser más fácil y poderosa.
Aquí hay un ejemplo de cómo usar la API Fetch para obtener datos de un servidor:
fetch('https://some-api.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
En este ejemplo,
- Se utiliza el método
fetch()
para hacer una solicitud al servidor ubicado en "https://some-api.com/data". El valor devuelto es una promesa que se cumplirá con la respuesta del servidor. - Se llama al primer método
.then()
para consumir la promesa y extraer datos JSON de la respuesta. - El siguiente método
then()
se llama para registrar en la consola los datos extraídos. - Si se produce algún error, este será capturado por el método
catch()
y se imprimirá por consola.
Espero que esta explicación ayude a aclarar cualquier confusión sobre la API Fetch y permita comprender mejor los ejemplos proporcionados en este artículo.
Funciones Asíncronas con async
/await
Async/Await
es una función que permite escribir código asíncrono de una manera más sincrónica y legible.
async
es una palabra clave que se utiliza para declarar una función como asíncrona.await
es una palabra clave que se utiliza dentro de una funciónasync
para pausar la ejecución de la función hasta que se resuelva una promesa.
Aquí, un ejemplo de cómo puede usar async/await
:
async function obtenerDatos() {
const respuesta = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const datos = await respuesta.json();
console.log(datos);
}
obtenerDatos();
En este ejemplo,
- Se declara la función
obtenerDatos
como una función asincrónica utilizando la palabra claveasync
. - Dentro de la función asincrónica, utilizamos la palabra clave
await
para esperar a que se complete la funciónfetch
y recuperar algunos datos de una API. - Una vez que se obtienen los datos, volvemos a usar
await
para esperar y analizar los datos recuperados como JSON. - Por último, imprimimos los datos en la consola.
"Aync/Await" es una herramienta poderosa para manejar operaciones asíncronas. Al eliminar las callbacks y proporcionar una forma más intuitiva de manejar operaciones asíncronas, el uso de "async/await" resulta en un código más legible y fácil de mantener.
Usando la palabra clave "async" antes de la definición de una función y la palabra clave "await" antes de una operación asíncrona hace que el código parezca más como código sincrónico, lo que lo hace más fácil de entender.
En general, "Async/Await" es un conocimiento valioso para la caja de herramientas del desarrollador de JavaScript y puede simplificar de manera significativa el manejo de operaciones asíncronas en tu código.
Conclusión
En resumen, la programación asíncrona es un concepto esencial en JavaScript que permite que tu código se ejecute en segundo plano sin bloquear la ejecución de otro código.
Los desarrolladores pueden crear aplicaciones más eficientes y receptivas utilizando funciones como callbacks, async/await y promesas.
La programación asíncrona puede ser difícil de entender al principio. Pero con la práctica y una comprensión sólida de los conceptos, se convierte en una herramienta poderosa para crear aplicaciones web de alto rendimiento.
¡Gracias por leer este artículo!
Si disfrutaste de este artículo y quieres aprender más sobre programación, sígueme en Instagram @alege_dev, donde publico regularmente consejos sobre varios temas de programación.