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
b1j935dg72g9u8zvh2oi

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?

3

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

4

¿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.

5

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.

6

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.
7
  • 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.
8

Ejemplos de código síncrono y asíncrono

9

Antes de empezar este proyecto, vamos a ver algunos ejemplos para resolver algunas dudas.

10

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: 👇

11
12

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: 👇

13

Ahora sabes la diferencia entre operaciones síncronas y asíncronas, vamos a construir nuestra tienda de helados.

14

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.

15

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:

16

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.

17

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.

Alt Text

Para explicarlo con más detalle, comencemos nuestro negocio de heladerías.

Pero espera...

Alt Text

La tienda tendrá dos partes:

  • El almacén tendrá todos los ingredientes [Nuestro Backend]
  • Vamos a producir helado en tu cocina [el frontend]
Alt Text

Almacenemos nuestros datos

Ahora, vamos a almacenar nuestros ingredientes dentro de un objeto. ¡Empecemos!

Alt Text

Puedes almacenar los ingredientes dentro de objetos como este:

let stocks = {
    Fruits : ["strawberry", "grapes", "banana", "apple"]
 }

Nuestros otros ingredientes están aquí: 👇

Alt Text

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)
Alt Text

Así es como funciona todo: 👇

Alt Text
Obtén el pedido del cliente, busca los ingredientes, comienza la producción y luego sirve.

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 👇

Alt Text

Tómate un descanso

Hasta ahora todo bien: ¡tómate un descanso!

Alt Text

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.

Tabla contiene los pasos para hacer helados
Chart contains steps to make ice cream

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.

Alt Text
Syntax of a setTimeout() function

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.

Alt Text

Si te estás preguntando cómo recogimos la fresa de nuestra variable de stock, aquí está el código con el formato.👇

Alt Text

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👇

Alt Text

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👇

Alt Text

Si lo recuerdas, aquí están nuestros pasos:

Alt Text
Chart contains steps to make ice cream

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 👇

Alt Text

¿Te sientes confundido?

Alt Text

Esto se llama infierno de función callback. Se ve algo como esto (¿recuerdas ese código justo arriba?): 👇

Alt Text
Illustration of Callback hell

¿Cuál es la solución a esto?

Cómo usar promesas para escapar del infierno de función callback

Alt Text

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!

Alt Text

Así es como se ve una promesa:

Alt Text
illustration of a promise format

Diseccionemos las promesas juntos.

Alt Text
Alt Text
An illustration of the life of a promise

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...

Alt Text

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

Alt Text
Chart contains steps to make ice cream

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") )

    }

  })
}
Alt Text

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í:

Alt Text

¡Buen trabajo!

Alt Text

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 👇

Alt Text
Illustration of promise chaining using .then handler

El manejador .then deveuelve una promesa cuando se resuelve nuestra promesa original.

Aquí hay un ejemplo:

Alt Text

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: 👇

Alt Text

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: 👇

Alt Text

Manejo de errores

Necesitamos una manera de manejar los errores cuando algo sale mal. Pero primero, necesitamos entender el ciclo de promesas:

Alt Text
Alt Text
An illustration of the life of a promise

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:👇

Alt Text

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()

Alt Text

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:👇

Alt Text

Todos, por favor den la bienvenida a Async / Await~

¿Cómo funciona Async / Await en JavaScript?

Alt Text

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

Alt Text

Echemos un vistazo👇

Alt Text

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....

Alt Text

Necesitas entender ->

  • Como usar las palabras clave try y catch
  • 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

Alt Text

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

Alt Text

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:

Alt Text

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

Alt Text

Vamos a crear dos funciones ->

  • kitchen: para hacer helado
  • time: 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:👇

Alt Text

El resultado se ve así cuando la tienda está cerrada: 👇

Alt Text

Hasta ahora todo bien.

Alt Text

Completemos nuestro proyecto.

Aquí está la lista de nuestras tareas de nuevo: 👇

Alt Text
Chart contains steps to make ice cream

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: 👇

Alt Text

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 ❤️

Alt Text

YouTube / Joy Shaheb

LinkedIn / JoyShaheb

Twitter / JoyShaheb

Instagram / JoyShaheb

Créditos