Artículo original escrito por Joy Shaheb.
Artículo original JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await in JS by Making Ice Cream ???
Traducido y adaptado por Mitchell Contreras
Hoy vamos a construir una tienda de helados y aprenderemos asincronismo con JavaScript al mismo tiempo. A lo largo del camino aprenderemos:
- Callbacks
- Promesas
- Async/Await
Aquí podremos ver que cubriremos en este artículo:
- ¿Qué es asíncrono en JavaScript?
- Síncrono vs asíncrono
- Como trabajan los callbacks en JavaScript
- Como Trabajan las promesas en JavaScript
- Como trabaja Async/Await en JavaScript
Entonces, vamos con esto.
Si prefieres puedes ver esté tutorial en YouTube
¿Qué es asíncrono en JavaScript?
Si quieres construir proyectos eficientemente, entonces esté concepto es para ti.
La teoría de asincronismo en JavaScript nos ayuda a dividir un proyecto complejo en tareas más pequeñas.
Podemos usar cualquiera de estas tres técnicas- callback, promesas o async/await para ejecutar estas pequeñas tareas de la manera de obtener los mejores resultados.
Vamos a empezar ?️
Síncrono vs asíncrono
¿Qué es un sístema síncrono?
En un sístema síncrono una tarea es terminada después de otra. Pensemos que tienes 10 tareas por completar, Debemos completar una tarea a la vez.
Observa el GIF ? - Una cosa está ocurriendo a la vez.
Veríamos que mientras la primera imagen no está cargada completamente, la segunda imagen no está cargando.
Bueno, JavaScript por defecto es síncrono (posee un solo hilo de ejecución). Pensemos que eso es - un hilo significa que tienes una sola mano para hacer las cosas.
¿Qué es un sistema asíncrono?
En este sistema, las tareas son completadas independientemente.
Aquí, imagina que 10 tareas, tú tienes 10 manos. Entonces, cada mano puede hacer una tarea independientemente de las otras manos.
Echa un vistazo al siguiente GIF ? - puedes ver que cada imagen carga al mismo tiempo.
otra vez, todas las imágenes son cargadas a su propio ritmo. Ninguna de ellas estás esperando por alguna otra.
Para resumir síncrono vs asíncrono JS:
Cuando tres imágenes están en un maratón, en un:
- Sistema síncrono: tres imágenes están en el mismo carril, ninguno puede adelantar al otro. La carrera es terminada uno a la vez, Si la imagen número dos se detiene, las siguientes imágenes se detienen.
- Sistema asíncrono: Las tres imágenes están en diferentes carriles. Ellas finalizarían la carrera en su propio carril. Ninguna se detiene por otra.
Ejemplos de código síncrono y asíncrono
Antes de empezar este proyecto, vamos a ver algunos ejemplos para resolver algunas dudas.
Vamos a probar un sistema asíncrono, escribe este código en JavaScript:
console.log(" Yo ");
console.log(" como ");
console.log(" Helado ");
Este es el resultado en la consola: ?
Vamos a tomarnos dos segundos para comernos el helado. Ahora, vamos a probar un sistema asíncrono. Escribe el siguiente código en JavaScript.
Observa: No te preocupes, discutiremos setTimeout()
luego en este artículo.
console.log("Yo");
// Veremos esto en 2 segundos
setTimeout(()=>{
console.log("como");
},2000)
console.log("Helado")
Este es el resultado en la consola: ?
Ahora sabes la diferencia entre operaciones síncronas y asíncronas, vamos a construir nuestra tienda de helados.
Para este proyecto puedes abrir codepen.io y empezar a programar, o puedes hacerlo en VS code y editar tu archivo de elección.
Abre la sección de JavaScript y también abre la consola de desarrollador. Escribiremos nuestro código y vemos los resultados en la consola.
Cuando pasamos una función a otra función como un argumento, esto lo llamamos callback.
Aquí podemos ver un ejemplo de una función callback:
No te preocupes, vamos a ver algunos ejemplos de funciones callback en un minuto.
¿Por qué usamos funciones callback?
Cuando hacemos una tarea compleja, dividimos esa tarea en pasos más pequeños. Para ayudarnos a establecer una relación entre estos pasos según el tiempo (opcional) y el orden, utilizamos devoluciones de llamada.
Estos son los pequeños pasos que debes tomar para hacer helado. También ten en cuenta que en este ejemplo, el orden de los pasos y el tiempo son cruciales. No puedes cortar la fruta y servir helado.
Al mismo tiempo, si un paso anterior no se completa, no podemos pasar al siguiente paso.
Para explicarlo con más detalle, comencemos nuestro negocio de heladerías.
Pero espera...
La tienda tendrá dos partes:
- El almacén tendrá todos los ingredientes [Nuestro Backend]
- Vamos a producir helado en tu cocina [el frontend]
Almacenemos nuestros datos
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 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 un cliente ordene. Una vez que tenemos un pedido, comenzamos la producción y luego servimos helado. Por lo tanto, vamos a crear dos funciones ->
order
(orden)production
(producción)
Así es como funciona todo: ?
Hagamos nuestras funciones. Usaremos funciones de flecha aquí:
let order = () =>{};
let production = () =>{};
Ahora, vamos a establecer una relación entre estas dos funciones usando una función callback, como esta: ?
let order = (call_production) =>{
call_production();
};
let production = () =>{};
Vamos a hacer una pequeña prueba
Usaremos la función console.log()
para realizar pruebas para 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 llamada production
como su argumento.
// name ? of our second function
order(production);
Aquí está el resultado en nuestra consola ?
Tómate un descanso
Hasta ahora todo bien: ¡tómate un descanso!
Despeja la consola console.log
Mantén este código y elimina todo [no elimines nuestra variable de acciones]. En nuestra primera función, pasa otro argumento para que podamos recibir la orden [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 tardará cada paso en ejecutarse.
En este gráfico, puedes ver que el paso 1 es realizar el pedido, que tarda 2 segundos. Luego, 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 ingredientes (3 segundos) y el paso 7, el paso final, es servir el helado que toma 2 segundos.
Para establecer el tiempo, la función setTimeout()
es excelente, ya que también utiliza una función callback tomando una función como argumento.
Ahora, vamos a seleccionar nuestra fruta y utilizar 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 en la consola: ?
Ten en cuenta que el resultado se muestra después de 2 segundos.
Si te estás preguntando cómo recogimos la fresa de nuestra variable de stock, aquí está el código con el formato.?
No borres nada. Ahora comenzaremos a escribir nuestra función de producción con el siguiente código.? Usaremos funciones de 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 picar 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 lo recuerdas, aquí están nuestros pasos:
Completemos nuestra producción de helados anidando una función dentro de otra función: Esto también se conoce como función callback, ¿recuerdas?
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 ?
¿Te sientes confundido?
Esto se llama infierno de función callback. Se ve algo como esto (¿recuerdas ese código justo arriba?): ?
¿Cuál es la solución a esto?
Cómo usar promesas para escapar del infierno de función callback
Las promesas se inventaron para resolver el problema del infierno de devolución de llamada y para manejar mejor nuestras tareas.
Tómate un descanso
¡Pero primero, tómate un descanso!
Así es como se ve una promesa:
Diseccionemos las promesas juntos.
Como muestran los gráficos anteriores, una promesa tiene tres estados:
- Pendiente: Esta es la etapa inicial. Aquí no pasa nada. Piensa en ello de esta manera, tu cliente se está tomando su tiempo para darte un pedido. Pero aún no han pedido nada.
- Resuelto: Esto significa que su cliente ha recibido su comida y está feliz.
- Rechazado: Esto significa que su cliente no recibió su pedido y abandonó el restaurante.
Adoptemos promesas para nuestro estudio de caso de producción de helados.
Pero espera...
Necesitamos entender cuatro cosas más primero ->
- Relación entre el tiempo y el trabajo
- Encadenamiento de promesas
- Manejo de errores
- El controlador
.finally
Comencemos nuestra heladería y entendamos cada uno de estos conceptos uno por uno dando pequeños pasos.
Relación entre el tiempo y el trabajo
Si lo recuerdas, estos son nuestros pasos y el tiempo que cada uno tarda en hacer helado
Para que esto suceda, vamos a crear una variable en JavaScript: ?
let is_shop_open = true;
Ahora cree una función llamada order
y pasa dos argumentos llamados time, work
:
let order = ( time, work ) =>{
}
Ahora, vamos a hacerle una promesa a nuestro cliente, "Te serviremos helado" Así ->
let order = ( time, work ) =>{
return new Promise( ( resolve, reject )=>{ } )
}
Nuestra promesa tiene 2 partes:
- Resuelto [ helado entregado ]
- Rechazado [ el cliente no recibió helado ]
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 sentencia if
. Sígueme ?
Nota: En la vida real, también puede evitar el factor tiempo. Esto depende completamente de la naturaleza de su 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, vamos a usar nuestra función recién creada para comenzar la producción de helados.
// Set ? time here
order( 2000, ()=>console.log(`${stocks.Fruits[0]} was selected`))
// pass a ☝️ function here to start working
Resultado ? después de 2 segundos se ve así:
¡Buen trabajo!
Encadenamiento de promesas
En este método, definimos lo que necesitamos hacer cuando se completa la primera tarea utilizando el manejador .then
. Se ve algo como esto ?
El manejador .then
deveuelve una promesa cuando se resuelve nuestra promesa original.
Aquí hay un ejemplo:
Déjame hacerlo más simple: es similar a dar instrucciones a alguien. Le dices a alguien que " Primero haga esto, luego haga aquello, luego esta otra cosa, luego.., entonces.., entonces..."y así sucesivamente.
- La primera tarea es nuestra promesa original.
- El resto de las tareas devuelven nuestra promesa una vez que se complete un pequeño trabajo.
Vamos a implementar esto en nuestro proyecto. En la parte inferior de su código escriba las siguientes líneas. ?
Nota: no te olvides de escribir la palabra return
dentro de tu manejador .then
. De lo contrario, no funcionará correctamente. Si tienes curiosidad, intenta eliminar la devolución una vez que 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: ?
Manejo de errores
Necesitamos una manera de manejar los errores cuando algo sale mal. Pero primero, necesitamos entender el ciclo de promesas:
Para atrapar nuestros errores, cambiemos nuestra variable a falso false
.
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 manejador .catch
. igual que .then
, también devuelve una promesa, pero solo cuando nuestra promesa original es rechazada.
Un pequeño recordatorio aquí:
.then
funciona cuando se resuelve una promesa.catch
funciona cuando se rechaza una promesa
Ir hasta el fondo y escribir el siguiente código:?
Solo recuerda que no debe haber nada entre tu anterior manejador .then
y el manejador .catch
.
.catch(()=>{
console.log("Customer left")
})
Aquí está el resultado:?
Un par de cosas a tener en cuenta sobre este código:
- El 1.er mensaje viene de la parte
reject()
de nuestra promesa - El segundo mensaje viene de manejador
.catch
Cómo usar el manejador .finally()
Hay algo llamado el manejador finally
el cual funciona sin importar si nuestra promesa fue resulta o rechazada.
Por ejemplo: Ya sea que atendamos a cero clientes o 100 clientes, nuestra tienda cerrará al final del día.
Si tienes curiosidad por probar esto, ven al fondo y escribe este código: ?
.finally(()=>{
console.log("end of day")
})
El resultado:?
Todos, por favor den la bienvenida a Async / Await~
¿Cómo funciona Async / Await en JavaScript?
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.
Pero primero, tomate un descaso
Echemos un vistazo?
Promesas vs Async/Await en JavaScript
Antes de async/await, para hacer una promesa escribimos esto:
function order(){
return new Promise( (resolve, reject) =>{
// Write code here
} )
}
Ahora usando async/await, escribimos uno como este:
//? the magical keyword
async function order() {
// Write code here
}
Pero espera....
Necesitas entender ->
- Como usar las palabras clave
try
ycatch
- Como usar la palabra clave
await
.
Cómo usar las palabras clave Try and Catch
Usamos la palabra clave try
para ejecutar nuestro código mientras usamos catch
para detectar nuestros errores. Es el mismo concepto que vimos cuando con promesas.
Hagamos una comparación. Veremos una pequeña demostración del formato, luego comenzaremos a programar.
Promesas en JS -> resolver resolve
o rechazar reject
We used resolve and reject in promises like this:
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]
Async / Await en JS -> try, catch
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 te asustes, hablaremos de la palabra clave await
a continuación.
Now hopefully you understand the difference between promises and Async / Await.
Cómo usar la palabra clave await
de JavaScript
La palabra clave await
hace que JavaScript espere hasta que una promesa se establezca y devuelva su resultado.
Cómo usar la palabra clave await
en JavaScript
Volvamos a nuestra heladería. No sabemos qué relleno prefiere un cliente, chocolate o cacahuetes. Así que tenemos que detener nuestra máquina e ir y preguntar a nuestro cliente lo que les gustaría en su helado.
Observe aquí que solo nuestra cocina está detenida, pero nuestro personal fuera de la cocina seguirá haciendo cosas como:
- lavar los platos
- limpiar las mesas
- tomando órdenes, y así sucesivamente
Un ejemplo de código de palabra clave await
Vamos a crear una pequeña promesa para preguntar qué relleno 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, vamos a crear nuestra función de cocina con la palabra clave async
primero.
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 función 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 opción de cobertura?" Mientras tanto, otras cosas todavía se hacen.
Una vez que tengamos su elección, entramos en la cocina y terminamos el trabajo.
Nota pequeña
Al usar Async / Await, también puede usar el .then
, .catch
, y .finally
, los manejadores también son una parte central de las promesas.
Abramos nuestra heladería de nuevo
Vamos a crear dos funciones ->
kitchen
: para hacer heladotime
: para asignar la cantidad de tiempo que tomará cada pequeña tarea.
¡Empecemos! Primero, cree la función de tiempo:
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 nuestra cocina:
async function kitchen(){
try{
// instruction here
}
catch(error){
// error management here
}
}
// Trigger
kitchen();
Vamos a dar pequeñas instrucciones y probar si nuestra función de cocina está funcionando 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 ahora todo bien.
Completemos nuestro proyecto.
Aquí está la lista de nuestras tareas de nuevo: ?
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: ?
Conclusión
¡Felicitaciones por leer hasta el final! En este artículo has aprendido:
- La diferencia entre sistemas síncronos y asíncronos
- Mecanismos de JavaScript asíncrono usando 3 técnicas (funciones callbacks, promesas, y Async / Await)
Aquí está tu medalla por leer hasta el final. ❤️
Las sugerencias y críticas son muy apreciadas ❤️
YouTube / Joy Shaheb
LinkedIn / JoyShaheb
Twitter / JoyShaheb
Instagram / JoyShaheb