Translation into Spanish of an interesting article by Joy Shaheb, software engineer at freeCodeCamp, css, nodejs, reactjs, bootstrap, and javascript expert from Dhaka, Bangladesh.
A free translation by Chema, a Spain-based translator specializing in English to Spanish translations
An original text written by Joy Shaheb, originally published in
https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/
* * *
Hoy vamos a construir y administrar una heladería y aprender JavaScript asíncrono al mismo tiempo. En el camino, aprenderás a usar:
¡Vamos allá!
Si deseas crear proyectos de manera eficiente, te interesa este concepto.
La teoría de JavaScript asíncrono ayuda a dividir grandes proyectos complejos en tareas más pequeñas.
Luego, puedes usar cualquiera de estas tres técnicas: callbacks, promesas o Async/await , para ejecutar esas pequeñas tareas para obtener mejores resultados.
¡Vamos con ello! 🎖️
En un sistema síncrono, las tareas se completan una tras otra.
Piensa en esto como si tuvieras una sola mano para realizar 10 tareas. En un caso así, sólo podrías completar una tarea a la vez.
Echa un vistazo al GIF 👇:
Verás que hasta que la primera imagen no se carga por completo, la segunda imagen no comienza a cargarse.
JavaScript es por defecto Synchronous [single threaded]. Míralo de esta manera: un hilo es una mano con la que hacer cosas.
En este sistema, las tareas se completan de forma independiente.
Aquí, imagina que para 10 tareas, tienes 10 manos. Así, cada mano puede hacer cada tarea de forma independiente y al mismo tiempo.
Echa un vistazo al GIF 👇: todas las imágenes se cargan al mismo tiempo.
Nuevamente, todas las imágenes se cargan a su propio ritmo. Ninguna de ellas está esperando a ninguna de las otras.
Analicemos las diferencias imaginando 3 imágenes en un maratón:
Antes de comenzar con nuestro proyecto, veamos algunos ejemplos y despejemos cualquier duda.
Para probar un sistema síncrono, escribe este código en JavaScript:
console.log(" I ");
console.log(" eat ");
console.log(" Ice Cream ");
Este sería el resultado: 👇
Digamos que se tardan dos segundos en comer un helado. Ahora, probemos un sistema asíncrono. Escribe el siguiente código en JavaScript.
Nota: No te preocupes, hablaremos de la función setTimeout()
más adelante.
console.log("I");
// This will be shown after 2 seconds
setTimeout(()=>{
console.log("eat");
},2000)
console.log("Ice Cream")
Y aquí está el resultado en la consola: 👇
Ahora que conoces la diferencia entre operaciones síncronas y asíncronas, montemos nuestra heladería.
Para este proyecto, puedes simplemente abrir Codepen.io y comenzar a programar, o bien, puedes hacerlo en VS o en el editor de tu elección.
Abrela sección de JavaScript y tu consola de desarrollador. Escribiremos nuestro código y veremos los resultados en la consola.
Cuando se anida una función dentro de otra función como argumento, a eso se le llama callback, que podemos traducir como llamada de vuelta o devolución de llamada.
Aquí hay una ilustración de un callback:
No te preocupes, veremos ejemplos de callbacks enseguida
Para hacer una tarea compleja, lo mejor es dividirla en pasos más pequeños. Para ayudarnos a establecer una relación entre estos pasos según el tiempo (opcional) y el orden, usamos callbacks.
Echa un vistazo a este ejemplo: 👇
Estos son los pasos que debes seguir para hacer helado. Ten en cuenta que en este ejemplo, el orden de los pasos y el tiempo son cruciales. No puedes simplemente picar la fruta y servir helado.
De la misma forma, si no se completa un paso anterior, no se puede pasar al siguiente paso.
Para explicar esto con más detalle, comencemos nuestro negocio de heladería.
La tienda tendrá dos partes:
Ahora, vamos a almacenar nuestros ingredientes dentro de un objeto. ¡Empecemos!
Puedes almacenar los ingredientes dentro de objetos como este: 👇
let stocks = {
Fruits : ["strawberry", "grapes", "banana", "apple"]
}
Nuestros otros ingredientes están aquí: 👇
Puedes almacenar estos otros ingredientes en objetos de JavaScript como este: 👇
let stocks = {
Fruits : ["strawberry", "grapes", "banana", "apple"],
liquid : ["water", "ice"],
holder : ["cone", "cup", "stick"],
toppings : ["chocolate", "peanuts"],
};
Todo el negocio depende de lo que pide un cliente . Una vez que tenemos un pedido, comenzamos la producción y luego servimos el helado. Para ello, crearemos dos funciones ->
order
production
Así es como funciona todo: 👇
Hagamos nuestras funciones. Usaremos funciones flecha :
let order = () =>{};
let production = () =>{};
Ahora, establezcamos una relación entre estas dos funciones usando una devolución de llamada, así: 👇
let order = (call_production) =>{
call_production();
};
let production = () =>{};
Usaremos la función console.log()
para realizar pruebas y aclarar cualquier duda que podamos tener sobre cómo establecimos la relación entre las dos funciones.
let order = (call_production) =>{
console.log("Order placed. Please call production")
// function 👇 is being called
call_production();
};
let production = () =>{
console.log("Production has started")
};
Para ejecutar la prueba, llamaremos a la función order
. Y agregaremos la segunda función llamadaproduction
como su argumento.
// name 👇 of our second function
order(production);
Aquí está el resultado 👇
Hasta ahora todo bien, ¡tómate un descanso!
Manten este código y elimina todo lo demás [no elimines nuestra variable de stock]. En nuestra primera función, pasa otro argumento para que podamos recibir el pedido [Nombre de la fruta]:
// Function 1
let order = (fruit_name, call_production) =>{
call_production();
};
// Function 2
let production = () =>{};
// Trigger 👇
order("", production);
Estos son nuestros pasos y el tiempo que llevará ejecutar cada paso.
En este gráfico, puedes ver que el paso 1 es realizar el pedido, lo que demora 2 segundos. El paso 2 es cortar la fruta (2 segundos), el paso 3 es agregar agua y hielo (1 segundo), el paso 4 es encender la máquina (1 segundo), el paso 5 es seleccionar el recipiente (2 segundos), el paso 6 es seleccionar los toppings (3 segundos) y el paso 7, el paso final, es servir el helado (2 segundos).
Para establecer el tiempo, la función setTimeout()
es excelente ya que también utiliza un callback al tomar una función como argumento.
Ahora, seleccionemos nuestra fruta y usemos esta función:
// 1st Function
let order = (fruit_name, call_production) =>{
setTimeout(function(){
console.log(`${stocks.Fruits[fruit_name]} was selected`)
// Order placed. Call production to start
call_production();
},2000)
};
// 2nd Function
let production = () =>{
// blank for now
};
// Trigger 👇
order(0, production);
Y aquí está el resultado : 👇
Ten en cuenta que el resultado tardará 2 segundos.
Si te preguntas cómo elegimos fresa de nuestra variable de stock, aquí está el código 👇
No borres nada. Ahora comenzaremos a escribir nuestra función de producción con el siguiente código. 👇 Usaremos funciones flecha:
let production = () =>{
setTimeout(()=>{
console.log("production has started")
},0000)
};
Y aquí está el resultado 👇
Anidaremos otra función setTimeout
en nuestra función setTimeout
existente para cortar la fruta. Así: 👇
let production = () =>{
setTimeout(()=>{
console.log("production has started")
setTimeout(()=>{
console.log("The fruit has been chopped")
},2000)
},0000)
};
Y aquí está el resultado 👇
Si recuerdas, estos son nuestros pasos:
Completemos nuestra producción de helado anidando una función dentro de otra función; algo que se conoce como callback, ¿te suena..?
let production = () =>{
setTimeout(()=>{
console.log("production has started")
setTimeout(()=>{
console.log("The fruit has been chopped")
setTimeout(()=>{
console.log(`${stocks.liquid[0]} and ${stocks.liquid[1]} Added`)
setTimeout(()=>{
console.log("start the machine")
setTimeout(()=>{
console.log(`Ice cream placed on ${stocks.holder[1]}`)
setTimeout(()=>{
console.log(`${stocks.toppings[0]} as toppings`)
setTimeout(()=>{
console.log("serve Ice cream")
},2000)
},3000)
},2000)
},1000)
},1000)
},2000)
},0000)
};
Y aquí está el resultado en la consola 👇
¿Mareado..?
Esto se llama infierno de callbacks. Se parece a esto (¿recuerdas el código justo arriba?): 👇
¿Cuál es la solución?
Las promesas se inventaron para resolver el problema del infierno de callbacksa y para gestionar mejor nuestras tareas.
Pero primero, ¡tómate un descanso!
Así es como se ve una promesa:
Analicemos las promesas juntos.
Como muestran los gráficos anteriores, una promesa tiene tres estados:
Adoptemos promesas para nuestro estudio de la producción de helados.
Necesitamos entender cuatro cosas más primero ->
.finally
Comencemos nuestra heladería y entendamos cada uno de estos conceptos uno por uno, poco a poco.
Si recuerdas, estos son nuestros pasos y el tiempo que tarda cada uno.
Para que esto suceda, creemos una variable en JavaScript: 👇
let is_shop_open = true;
Ahora crea una función llamada order
y pasa dos argumentos llamados time, work
:
let order = ( time, work ) =>{
}
Ahora, le haremos una promesa a nuestro cliente: “Le serviremos helado”. Así ->
let order = ( time, work ) =>{
return new Promise( ( resolve, reject )=>{ } )
}
Nuestra promesa tiene 2 partes:
let order = ( time, work ) => {
return new Promise( ( resolve, reject )=>{
if( is_shop_open ){
resolve( )
}
else{
reject( console.log("Our shop is closed") )
}
})
}
Agreguemos los factores de tiempo y trabajo dentro de nuestra promesa usando una función setTimeout()
dentro de nuestra declaraciónif
. Sígueme 👇
Nota: en la vida real, también puedes evitar el factor tiempo. Esto depende completamente de la naturaleza de tu trabajo.
let order = ( time, work ) => {
return new Promise( ( resolve, reject )=>{
if( is_shop_open ){
setTimeout(()=>{
// work is 👇 getting done here
resolve( work() )
// Setting 👇 time here for 1 work
}, time)
}
else{
reject( console.log("Our shop is closed") )
}
})
}
Ahora, usaremos nuestra función recién creada para comenzar la producción de helado.
// Set 👇 time here
order( 2000, ()=>console.log(`${stocks.Fruits[0]} was selected`))
// pass a ☝️ function here to start working
El resultado 👇 después de 2 segundos se ve así:
¡Buen trabajo!
En este método, definimos lo que debemos hacer cuando se completa la primera tarea usando el controlador .then
. Se parece a esto 👇
El controlador .then devuelve una promesa cuando se resuelve nuestra promesa original.
Permítanme hacerlo más simple: es similar a dar instrucciones a alguien. Le dices a alguien: “Primero haz esto, luego haz eso, luego esta otra cosa, luego…, luego…, luego…” y así sucesivamente.
Implementemos esto en nuestro proyecto. En la parte inferior de su código, escribe las siguientes líneas. 👇
Nota: no olvides escribir la palabra return
dentro de tu controlador .then
, de lo contrario, no funcionará correctamente. Si tienes curiosidad, prueba a eliminar el callback una vez terminemos los pasos:
order(2000,()=>console.log(`${stocks.Fruits[0]} was selected`))
.then(()=>{
return order(0000,()=>console.log('production has started'))
})
Y aquí está el resultado: 👇
Usando el mismo sistema, terminemos nuestro proyecto:👇
// step 1
order(2000,()=>console.log(`${stocks.Fruits[0]} was selected`))
// step 2
.then(()=>{
return order(0000,()=>console.log('production has started'))
})
// step 3
.then(()=>{
return order(2000, ()=>console.log("Fruit has been chopped"))
})
// step 4
.then(()=>{
return order(1000, ()=>console.log(`${stocks.liquid[0]} and ${stocks.liquid[1]} added`))
})
// step 5
.then(()=>{
return order(1000, ()=>console.log("start the machine"))
})
// step 6
.then(()=>{
return order(2000, ()=>console.log(`ice cream placed on ${stocks.holder[1]}`))
})
// step 7
.then(()=>{
return order(3000, ()=>console.log(`${stocks.toppings[0]} as toppings`))
})
// Step 8
.then(()=>{
return order(2000, ()=>console.log("Serve Ice Cream"))
})
Aquí está el resultado: 👇
Necesitamos una forma de gestionar los errores cuando algo sale mal. Pero primero, necesitamos entender el ciclo de la promesa:
Para detectar nuestros errores, cambiemos nuestra variable a falso.
let is_shop_open = false;
Lo que significa que nuestra tienda está cerrada. Ya no vendemos helados a nuestros clientes.
Para manejar esto, usamos el controlador .catch
. Al igual que .then
, también devuelve una promesa, pero solo cuando se rechaza nuestra promesa original.
Un pequeño recordatorio aquí:
.then
funciona cuando se resuelve una promesa.catch
funciona cuando una promesa es rechazadaBaja hasta el final y escribe el siguiente código:👇
Recuerda que no debe haber nada entre .then
y .catch
.
.catch(()=>{
console.log("Customer left")
})
Aquí está el resultado: 👇
Un par de cosas a tener en cuenta sobre este código:
reject()
de nuestra promesa.catch
.Hay algo llamado controlador finally
que funciona sin importar si nuestra promesa fue resuelta o rechazada.
Por ejemplo: si no atendemos a ningún cliente o a 100 clientes, nuestra tienda cerrará al final del día.
Si tienes curiosidad por probar esto, ven al final y escribe este código: 👇
.finally(()=>{
console.log("end of day")
})
El resultado:👇
Demos ahora todos la bienvenida a Async / Await ~
Se supone que esta es la mejor manera de escribir promesas y nos ayuda a mantener nuestro código simple y limpio.
Todo lo que tienes que hacer es escribir la palabra async
antes de cualquier función regular y se convierte en una promesa.
Echemos un vistazo: 👇
Antes de async/await, para hacer una promesa, escribimos esto:
function order(){
return new Promise( (resolve, reject) =>{
// Write code here
} )
}
Ahora usando async/await, escribiremos este:
//👇 the magical keyword
async function order() {
// Write code here
}
Necesitas entender ->
try
ycatch
Usamos try
para ejecutar nuestro código mientras que usamos catch
para detectar nuestros errores. Es el mismo concepto que vimos cuando miramos las promesas.
Veamos una comparación. Primero una pequeña demostración del formato, luego comenzaremos a programar.
Usamos resolver y rechazar en promesas como esta:
function kitchen(){
return new Promise ((resolve, reject)=>{
if(true){
resolve("promise is fulfilled")
}
else{
reject("error caught here")
}
})
}
kitchen() // run the code
.then() // next step
.then() // next step
.catch() // error caught here
.finally() // end of the promise [optional]
Cuando usamos async/await, usamos este formato:
//👇 Magical keyword
async function kitchen(){
try{
// Let's create a fake problem
await abc;
}
catch(error){
console.log("abc does not exist", error)
}
finally{
console.log("Runs code anyways")
}
}
kitchen() // run the code
No entres en pánico, discutiremos await
a continuación.
Ahora, con suerte, ya deberías entender la diferencia entre las promesas y Async/Await.
La palabra clave await
hace que JavaScript espere hasta que se establezca una promesa y devuelva su resultado.
Volvamos a nuestra heladería. No sabemos qué topping quiere un cliente: chocolate o frutas. Entonces, debemos detener nuestra máquina e ir a preguntarle a nuestro cliente qué le gustaría con su helado.
Observa aquí que solo se detiene nuestra cocina, pero nuestro personal fuera de la cocina seguirá haciendo cosas como:
Vamos a crear una pequeña promesa para preguntar qué topping usar. El proceso dura tres segundos.
function toppings_choice (){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve( console.log("which topping would you love?") )
},3000)
})
}
Ahora, creemos primero nuestra función kitchen con la palabra asinc.
async function kitchen(){
console.log("A")
console.log("B")
console.log("C")
await toppings_choice()
console.log("D")
console.log("E")
}
// Trigger the function
kitchen();
Agreguemos otras tareas debajo de la llamada kitchen()
.
console.log("doing the dishes")
console.log("cleaning the tables")
console.log("taking orders")
Y aquí está el resultado:
Literalmente estamos saliendo de nuestra cocina para preguntarle a nuestro cliente, “¿cuál es su elección de topping?” En paralelo, se hacen otras cosas.
Una vez que tenemos su elección de topping, entramos en la cocina y terminamos el trabajo.
Al usar Async/Await, también puedes usar los controladores .then
, .catch
y , .finally
que son una parte central de las promesas.
Vamos a crear dos funciones ->
kitchen
: hacer heladotime
: para asignar la cantidad de tiempo que llevará cada pequeña tarea.¡Empecemos! Primero, crea la función time
:
let is_shop_open = true;
function time(ms) {
return new Promise( (resolve, reject) => {
if(is_shop_open){
setTimeout(resolve,ms);
}
else{
reject(console.log("Shop is closed"))
}
});
}
Ahora, vamos a crear kitchen
:
async function kitchen(){
try{
// instruction here
}
catch(error){
// error management here
}
}
// Trigger
kitchen();
Demos pequeñas instrucciones y probemos si nuestra función de cocina funciona o no:
async function kitchen(){
try{
// time taken to perform this 1 task
await time(2000)
console.log(`${stocks.Fruits[0]} was selected`)
}
catch(error){
console.log("Customer left", error)
}
finally{
console.log("Day ended, shop closed")
}
}
// Trigger
kitchen();
El resultado se ve así cuando la tienda está abierta: 👇
El resultado se ve así cuando la tienda está cerrada: 👇
Hasta aquí todo bien.
Completemos nuestro proyecto.
Aquí está de nuevo la lista de tareas: 👇
Primero, abre nuestra tienda
let is_shop_open = true;
Ahora escribe los pasos dentro de nuestra función kitchen()
: 👇
async function kitchen(){
try{
await time(2000)
console.log(`${stocks.Fruits[0]} was selected`)
await time(0000)
console.log("production has started")
await time(2000)
console.log("fruit has been chopped")
await time(1000)
console.log(`${stocks.liquid[0]} and ${stocks.liquid[1]} added`)
await time(1000)
console.log("start the machine")
await time(2000)
console.log(`ice cream placed on ${stocks.holder[1]}`)
await time(3000)
console.log(`${stocks.toppings[0]} as toppings`)
await time(2000)
console.log("Serve Ice Cream")
}
catch(error){
console.log("customer left")
}
}
Y aquí está el resultado: 👇
¡Felicidades por leer hasta el final! En este artículo has aprendido:
Aquí está tu medalla por leer hasta el final. ❤️
YouTube / Alegría Shaheb
LinkedIn / JoyShaheb
Twitter / Joy Shaheb
Instagram / JoyShaheb