Programación Asincrónica - Eloquent JavaScript
Programación Asincrónica - Eloquent JavaScript
Chapter 11 ◀◆▶
Programación Asincrónica
“ Quién puede esperar tranquilamente mientras el barro se asienta?
Quién puede permanecer en calma hasta el momento de actuar?”
— Laozi, Tao Te Ching
La parte central de una computadora, la parte que lleva a cabo los pasos
individuales que componen nuestros programas, es llamada procesador. Los
programas que hemos visto hasta ahora son cosas que mantienen al
procesador ocupado hasta que hayan terminado su trabajo. La velocidad a la
que algo como un ciclo que manipule números pueda ser ejecutado, depende
casi completamente de la velocidad del procesador.
Pero muchos programas interactúan con cosas fuera del procesador. por
ejemplo, podrian comunicarse a través de una red de computadoras o solicitar
datos del disco duro—lo que es mucho más lento que obtenerlos desde la
memoria.
Cuando una cosa como tal este sucediendo, sería una pena dejar que el
procesador se mantenga inactivo—podría haber algún otro trabajo que este
pueda hacer en el mientras tanto. En parte, esto es manejado por tu sistema
https://eloquentjs-es.thedojo.mx/11_async.html 1/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Asincronicidad
https://eloquentjs-es.thedojo.mx/11_async.html 2/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
asynchronous
Otra forma de describir la diferencia es que esperar que las acciones terminen
es implicito en el modelo síncrono, mientras que es explicito, bajo nuestro
control, en el asincrónico.
Tecnología cuervo
La mayoría de las personas son conscientes del hecho de que los cuervos son
pájaros muy inteligentes. Pueden usar herramientas, planear con anticipación,
recordar cosas e incluso comunicarse estas cosas entre ellos.
https://eloquentjs-es.thedojo.mx/11_async.html 3/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Para comunicarse con otros dispositivos, estas máquinas usan señales de luz.
Los cuervos incrustan piezas de material reflectante en tallos de comunicación
especial, y los insectos apuntan estos para reflejar la luz hacia otro nido,
codificando los datos como una secuencia de flashes rápidos. Esto significa
que solo los nidos que tienen una conexión visual ininterrumpida pueden
comunicarse entre ellos.
https://eloquentjs-es.thedojo.mx/11_async.html 4/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Devolución de ll amadas
(Todos los nombres de las vinculaciones y los strings se han traducido del
lenguaje cuervo a Español.)
Cada mensaje está etiquetado con un tipo, que determina cómo este es
manejado. Nuestro código puede definir manejadores para tipos de solicitud
específicos, y cuando se recibe una solicitud de este tipo, se llama al
controlador para que este produzca una respuesta.
https://eloquentjs-es.thedojo.mx/11_async.html 6/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Pero para hacer nidos capaces de recibir esa solicitud, primero tenemos que
definir un tipo de solicitud llamado "nota" . El código que maneja las
solicitudes debe ejecutarse no solo en este nido-computadora, sino en todos
los nidos que puedan recibir mensajes de este tipo. Asumiremos que un cuervo
sobrevuela e instala nuestro código controlador en todos los nidos.
https://eloquentjs-es.thedojo.mx/11_async.html 7/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Promesas
https://eloquentjs-es.thedojo.mx/11_async.html 8/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Pero eso no es todo lo que hace el método then . Este retorna otra promesa,
que resuelve al valor que retorna la función del controlador o, si esa retorna
una promesa, espera por esa promesa y luego resuelve su resultado.
Es útil pensar acerca de las promesas como dispositivos para mover valores a
una realidad asincrónica. Un valor normal simplemente esta allí. Un valor
prometido es un valor que podría ya estar allí o podría aparecer en algún
momento en el futuro. Las computaciones definidas en términos de promesas
actúan en tales valores envueltos y se ejecutan de forma asíncrona a medida
los valores se vuelven disponibles.
Para crear una promesa, puedes usar Promise como un constructor. Tiene
una interfaz algo extraña—el constructor espera una función como argumento,
a la cual llama inmediatamente, pasando una función que puede usar para
resolver la promesa. Funciona de esta manera, en lugar de, por ejemplo, con
un método resolve , de modo que solo el código que creó la promesa pueda
resolverla.
almacenamiento(granRoble, "enemigos")
.then(valor => console.log("Obtuve", valor));
Fr acaso
Las promesas hacen esto más fácil. Estas pueden ser resueltas (la acción
termino con éxito) o rechazadas (esta falló). Los controladores de resolución
(registrados con then ) solo se llaman cuando la acción es exitosa, y los
rechazos se propagan automáticamente a la nueva promesa que es retornada
por then . Y cuando un controlador arroje una excepción, esto
automáticamente hace que la promesa producida por su llamada then sea
rechazada. Entonces, si cualquier elemento en una cadena de acciones
asíncronas falla, el resultado de toda la cadena se marca como rechazado, y no
se llaman más manejadores despues del punto en donde falló.
Al igual que resolver una promesa proporciona un valor, rechazar una también
proporciona uno, generalmente llamado la razón el rechazo. Cuando una
excepción en una función de controlador provoca el rechazo, el valor de la
excepción se usa como la razón. Del mismo modo, cuando un controlador
retorna una promesa que es rechazada, ese rechazo fluye hacia la próxima
promesa. Hay una función Promise.reject que crea una nueva promesa
inmediatamente rechazada.
https://eloquentjs-es.thedojo.mx/11_async.html 10/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Las cadenas de promesas creadas por llamadas a then y catch puede verse
como una tubería a través de la cual los valores asíncronicos o las fallas se
mueven. Dado que tales cadenas se crean mediante el registro de
controladores, cada enlace tiene un controlador de éxito o un controlador de
rechazo (o ambos) asociados a ello. Controladores que no coinciden con ese
tipo de resultados (éxito o fracaso) son ignorados. Pero los que sí coinciden
son llamados, y su resultado determina qué tipo de valor viene después—éxito
cuando retorna un valor que no es una promesa, rechazo cuando arroja una
excepción, y el resultado de una promesa cuando retorna una de esas.
Tal y como es, eso solo causará que la devolución de llamada dada a send
nunca sea llamada, lo que probablemente hará que el programa se detenga sin
https://eloquentjs-es.thedojo.mx/11_async.html 11/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
A menudo, las fallas de transmisión son accidentes aleatorios, como la luz del
faro de un auto interfieriendo con las señales de luz, y simplemente volver a
intentar la solicitud puede hacer que esta tenga éxito. Entonces, mientras
estamos en eso, hagamos que nuestra función de solicitud automáticamente
reintente el envío de la solicitud momentos antes de que se de por vencida.
Y, como hemos establecido que las promesas son algo bueno, también
haremos que nuestra función de solicitud retorne una promesa. En términos
de lo que pueden expresar, las devoluciones de llamada y las promesas son
equivalentes. Las funciones basadas en devoluciones de llamadas se pueden
envolver para exponer una interfaz basada en promesas, y viceversa.
Estos pueden ser traducidos para prometer resolución y rechazo por parte de
nuestra envoltura.
Debido a que las promesas solo se pueden resolver (o rechazar) una vez, esto
funcionara. La primera vez que se llame a resolve o reject se determinara
el resultado de la promesa y cualquier llamada subsecuente, como el tiempo de
espera que llega después de que finaliza la solicitud, o una solicitud que
regresa después de que otra solicitud es finalizada, es ignorada.
En general, no construiremos una red robusta de clase mundial hoy. Pero eso
esta bien—los cuervos no tienen expectativas muy altas todavía cuando se
trata de la computación.
https://eloquentjs-es.thedojo.mx/11_async.html 13/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
https://eloquentjs-es.thedojo.mx/11_async.html 14/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
function vecinosDisponibles(nido) {
let solicitudes = nido.vecinos.map(vecino => {
return request(nido, vecino, "ping")
.then(() => true, () => false);
});
return Promise.all(solicitudes).then(resultado => {
return nido.vecinos.filter((_, i) => resultado[i]);
});
}
Inundación de red
El hecho de que los nidos solo pueden hablar con sus vecinos inhibe en gran
cantidad la utilidad de esta red.
todosLados(nido => {
nido.estado.chismorreo = [];
});
Para evitar enviar el mismo mensaje a traves de la red por siempre, cada nido
mantiene un array de strings de chismorreos que ya ha visto. Para definir este
array, usaremos la función todosLados —que ejecuta código en todos los
nidos—para añadir una propiedad al objeto estado del nido, que es donde
mantendremos estado local del nido.
Esto provocará que una nueva pieza de chismes se propague a través de la red
como una mancha de tinta en agua. Incluso cuando algunas conexiones no
estan trabajando actualmente, si hay una ruta alternativa a un nido dado, el
chisme llegará hasta allí.
Enrutamiento de mensajes
https://eloquentjs-es.thedojo.mx/11_async.html 16/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Dado que cada nido solo conoce a sus vecinos directos, no tiene la información
que necesita para calcular una ruta. De alguna manera debemos extender la
información acerca de estas conexiones a todos los nidos. Preferiblemente en
una manera que permita ser cambiada con el tiempo, cuando los nidos son
abandonados o nuevos nidos son construidos.
https://eloquentjs-es.thedojo.mx/11_async.html 17/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
todosLados(nido => {
nido.estado.conexiones = new Map;
nido.estado.conexiones.set(nido.nombre, nido.vecinos);
difundirConexiones(nido, nido.nombre);
});
Una cosa que puedes hacer con grafos es encontrar rutas en ellos, como vimos
en el Capítulo 7. Si tenemos una ruta hacia el destino de un mensaje, sabemos
en qué dirección enviarlo.
https://eloquentjs-es.thedojo.mx/11_async.html 18/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Ahora podemos construir una función que pueda enviar mensajes de larga
distancia. Si el mensaje está dirigido a un vecino directo, se entrega
normalmente. Si no, se empaqueta en un objeto y se envía a un vecino que este
más cerca del objetivo, usando el tipo de solicitud "ruta" , que hace que ese
vecino repita el mismo comportamiento.
Funciones asíncronas
https://eloquentjs-es.thedojo.mx/11_async.html 19/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Para obtener una pieza de información dada que no este en su propia bulbo de
almacenamiento, una computadora nido puede consultar otros nidos al azar
en la red hasta que encuentre uno que la tenga.
function red(nido) {
return Array.from(nido.estado.conexiones.keys());
}
https://eloquentjs-es.thedojo.mx/11_async.html 20/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Una función async está marcada por la palabra async antes de la palabra
clave function . Los métodos también pueden hacerse async al escribir
async antes de su nombre. Cuando se llame a dicha función o método, este
retorna una promesa. Tan pronto como el cuerpo retorne algo, esa promesa es
resuelta Si arroja una excepción, la promesa es rechazada.
https://eloquentjs-es.thedojo.mx/11_async.html 21/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Para código asincrónico no-trivial, esta notación suele ser más conveniente
que usar promesas directamente. Incluso si necesitas hacer algo que no se
ajuste al modelo síncrono, como realizar múltiples acciones al mismo tiempo,
es fácil combinar await con el uso directo de promesas.
Gener adores
function* potenciacion(n) {
for (let actual = n;; actual *= n) {
yield actual;
}
}
https://eloquentjs-es.thedojo.mx/11_async.html 22/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Conjunto.prototype[Symbol.iterator] = function*() {
for (let i = 0; i < this.miembros.length; i++) {
yield this.miembros[i];
}
};
El ciclo de evento
Los programas asincrónicos son ejecutados pieza por pieza. Cada pieza puede
iniciar algunas acciones y programar código para que se ejecute cuando la
acción termine o falle. Entre estas piezas, el programa permanece inactivo,
esperando por la siguiente acción.
https://eloquentjs-es.thedojo.mx/11_async.html 23/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
try {
setTimeout(() => {
throw new Error("Woosh");
}, 20);
} catch (_) {
// Esto no se va a ejecutar
console.log("Atrapado!");
}
https://eloquentjs-es.thedojo.mx/11_async.html 24/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
Promise.resolve("Listo").then(console.log);
console.log("Yo primero!");
// → Yo primero!
// → Listo
Errores asincrónic os
https://eloquentjs-es.thedojo.mx/11_async.html 25/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
return lista;
}
La parte async nombre => muestra que las funciones de flecha también
pueden ser async al poner la palabra async delante de ellas.
Pero está seriamente roto. Siempre devolverá solo una línea de salida,
enumerando al nido que fue más lento en responder.
polluelos(granRoble, 2017).then(console.log);
Esto podría haberse evitado fácilmente retornando las líneas de las promesas
mapeadas y llamando a join en el resultado de Promise.all , en lugar de
construir la lista cambiando una vinculación. Como siempre, calcular nuevos
valores es menos propenso a errores que cambiar valores existentes.
https://eloquentjs-es.thedojo.mx/11_async.html 26/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
});
return (await Promise.all(lineas)).join("\n");
}
Errores como este son fáciles de hacer, especialmente cuando se usa await , y
debes tener en cuenta dónde se producen las brechas en tu código. Una
ventaja de la asincronicidad explicita de JavaScript (ya sea a través de
devoluciones de llamada, promesas, o await ) es que detectar estas brechas es
relativamente fácil.
Resumen
Ejercicios
Los cuervos del pueblo poseen un viejo bisturí que ocasionalmente usan en
misiones especiales—por ejemplo, para cortar puertas de malla o embalar
cosas. Para ser capaces de rastrearlo rápidamente, cada vez que se mueve el
bisturí a otro nido, una entrada se agrega al almacenamiento tanto del nido
que lo tenía como al nido que lo tomó, bajo el nombre "bisturí" , con su
nueva ubicación como su valor.
https://eloquentjs-es.thedojo.mx/11_async.html 27/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
function localizarBisturi2(nido) {
// Tu codigo aqui.
}
localizarBisturi(granRoble).then(console.log);
// → Tienda del Carnicero
» Display hints...
Implemente algo como esto tu mismo como una función regular llamada
Promise_all .
Recuerda que una vez que una promesa ha tenido éxito o ha fallado, no puede
tener éxito o fallar de nuevo, y llamadas subsecuentes a las funciones que
resuelven son ignoradas. Esto puede simplificar la forma en que manejas la
falla de tu promesa.
https://eloquentjs-es.thedojo.mx/11_async.html 28/29
12/3/24, 10:15 Programación Asincrónica :: Eloquent JavaScript
function Promise_all(promesa) {
return new Promise((resolve, reject) => {
// Tu codigo aqui.
});
}
// Codigo de Prueba.
Promise_all([]).then(array => {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(resolve => {
setTimeout(() => resolve(val), Math.random() * 500);
});
}
Promise_all([soon(1), soon(2), soon(3)]).then(array => {
console.log("This should be [1, 2, 3]:", array);
});
Promise_all([soon(1), Promise.reject("X"), soon(3)])
.then(array => {
console.log("We should not get here");
})
.catch(error => {
if (error != "X") {
console.log("Unexpected failure:", error);
}
});
» Display hints...
◀◆▶
https://eloquentjs-es.thedojo.mx/11_async.html 29/29