Tutorial de JavaScript: aprende a usar callbacks, promesas, y async/await en JS, mientras haces helado 🍧🍨🍦

Translation into Spanish of an interesting article by Joy Shaheb, software engineer at freeCodeCamp, css, nodejs, reactjs, bootstrap, and javascript expert from Dhaka, Bangladesh.

asyncawaitJavaScripttranslation into spanish
19 May, 2022 JavaScript Async/Await
19 May, 2022 JavaScript Async/Await

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:

  • Callbacks (función que se traduciría como “llamada de vuelta”)
  • Promises (“promesas”)
  • Async/Await (asíncrono / espera)
Texto alternativo

Esto es lo que cubriremos en este artículo:

  • ¿Qué es JavaScript asíncrono?
  • JavaScript síncrono vs asíncrono
  • Cómo funcionan los callbacks en JavaScript
  • Cómo funcionan las promesas en JavaScript
  • Cómo funciona Async / Await en JavaScript

¡Vamos allá!

También puedes ver este tutorial en YouTube si quieres:

¿Qué es JavaScript asíncrono?

Texto alternativo

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! 🎖️

JavaScript síncrono vs asíncrono

Texto alternativo

¿Qué es un Sistema Sincrono?

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

Sistema sincrónico

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.

¿Qué es un Sistema Asíncrono?

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.

Sistema Asíncrono

Nuevamente, todas las imágenes se cargan a su propio ritmo. Ninguna de ellas está esperando a ninguna de las otras.

Para resumir: Synchronous vs Asynchronous JS

Analicemos las diferencias imaginando 3 imágenes en un maratón:

  • Sistema síncrono, las 3 imágenes estarían en el mismo carril. Una no puede adelantar a la otra. La carrera se acaba uno a uno. Si la imagen número 2 se detiene, la siguiente imagen se detiene.
Texto alternativo
  • Sistema asíncrono, las 3 imágenes están en carriles diferentes. Cada una terminará la carrera a su ritmo. Nadie se detiene por nadie:
Texto alternativo

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

Texto alternativo

Antes de comenzar con nuestro proyecto, veamos algunos ejemplos y despejemos cualquier duda.

Ejemplo de código síncrono

Texto alternativo

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

Texto alternativo

Ejemplo de código asíncrono

Texto alternativo

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

Texto alternativo

Ahora que conoces la diferencia entre operaciones síncronas y asíncronas, montemos nuestra heladería.

Cómo configurar nuestro proyecto

Texto alternativo

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.

¿Qué son los callbacks en JavaScript?

Texto alternativo

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:

uz3pl56lmoc2pq7wzi2s
Un ejemplo de devolución de llamada

No te preocupes, veremos ejemplos de callbacks enseguida

¿Por qué usar callbacks?

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

Texto alternativo
Tabla con los pasos para hacer helado

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.

Texto alternativo

Para explicar esto con más detalle, comencemos nuestro negocio de heladería.

Pero espera…

Texto alternativo

La tienda tendrá dos partes:

  • El almacén tendrá todos los ingredientes [Nuestro Backend]
  • Produciremos helado en nuestra cocina [La interfaz]
Texto alternativo

Almacenemos nuestros datos

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

Texto alternativo

Puedes almacenar los ingredientes dentro de objetos como este: 👇

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

Nuestros otros ingredientes están aquí: 👇

Texto alternativo

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
Texto alternativo

Así es como funciona todo: 👇

Texto alternativo
Recibir el pedido del cliente, obtener los ingredientes, iniciar la producción y servir el helado.

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 = () =>{};

Hagamos una pequeña prueba

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 llamadaproductioncomo su argumento.

// name 👇 of our second function
order(production);

Aquí está el resultado 👇

Texto alternativo

Descanso

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

Texto alternativo

Limpiar el archivo console.log

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.

Texto alternativo
Tabla con los pasos para hacer helado

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.

Texto alternativo
Sintaxis de una función setTimeout()

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.

Texto alternativo

Si te preguntas cómo elegimos fresa de nuestra variable de stock, aquí está el código 👇

Texto alternativo

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 👇

Texto alternativo

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 👇

Texto alternativo

Si recuerdas, estos son nuestros pasos:

Texto alternativo
La tabla contiene los pasos para hacer helado

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 👇

Texto alternativo

¿Mareado..?

Texto alternativo

Esto se llama infierno de callbacks. Se parece a esto (¿recuerdas el código justo arriba?): 👇

Texto alternativo
Ilustración del infierno de callbacks

¿Cuál es la solución?

Cómo usar promesas para escapar del infierno de callbacks

Texto alternativo

Las promesas se inventaron para resolver el problema del infierno de callbacksa y para gestionar mejor nuestras tareas.

Descanso

Pero primero, ¡tómate un descanso!

Texto alternativo

Así es como se ve una promesa:

Texto alternativo
ilustración de un formato de promesa

Analicemos las promesas juntos.

Texto alternativo
Texto alternativo
Una ilustración de la vida de una promesa.

Como muestran los gráficos anteriores, una promesa tiene tres estados:

  • Pendiente: Esta es la etapa inicial. Aquí no pasa nada. Piénsalo de esta manera, tu cliente se está tomando su tiempo para darte un pedido. Pero todavía no han pedido nada.
  • Resuelto: Esto significa que su cliente ha recibido su comida y está feliz.
  • Rechazado: Esto significa que tu cliente no recibió su pedido y abandonó el restaurante.

Adoptemos promesas para nuestro estudio de la producción de helados.

Pero espera…

Texto alternativo

Necesitamos entender cuatro cosas más primero ->

  • Relación entre el tiempo y el trabajo.
  • Encadenamiento de promesas
  • Gestión de errores
  • el controlador.finally

Comencemos nuestra heladería y entendamos cada uno de estos conceptos uno por uno, poco a poco.

Relación entre tiempo y trabajo.

Si recuerdas, estos son nuestros pasos y el tiempo que tarda cada uno.

Texto alternativo
Tabla con los pasos para hacer helado

Para que esto suceda, creemos una variable en JavaScript: 👇

let is_shop_open = true;

Ahora crea una función llamada ordery 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:

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

    }

  })
}
Texto alternativo

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

Texto alternativo

¡Buen trabajo!

Texto alternativo

Encadenamiento de promesas

En este método, definimos lo que debemos hacer cuando se completa la primera tarea usando el controlador .then. Se parece a esto 👇

Texto alternativo
Ilustración del encadenamiento de promesas usando el controlador .then

El controlador .then devuelve una promesa cuando se resuelve nuestra promesa original.

Aquí hay un ejemplo:

Texto alternativo

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.

  • La primera tarea es nuestra promesa original.
  • El resto de las tareas devuelven nuestra promesa una vez que se completa un pequeño trabajo.

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

Texto alternativo

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

Texto alternativo

Gestión de errores

Necesitamos una forma de gestionar los errores cuando algo sale mal. Pero primero, necesitamos entender el ciclo de la promesa:

Texto alternativo
Texto alternativo
Una ilustración de la vida de una 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í:

  • .thenfunciona cuando se resuelve una promesa
  • .catchfunciona cuando una promesa es rechazada

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

Texto alternativo

Un par de cosas a tener en cuenta sobre este código:

  • El 1er mensaje viene de la parte reject()de nuestra promesa
  • El segundo mensaje proviene del controlador .catch.

Cómo usar el controlador .finally()

Texto alternativo

Hay algo llamado controlador finallyque 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:👇

Texto alternativo

Demos ahora todos la bienvenida a Async / Await ~

¿Cómo funciona Async/Await en JavaScript?

Texto alternativo

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 asyncantes de cualquier función regular y se convierte en una promesa.

Pero primero, tómate un descanso.

Texto alternativo

Echemos un vistazo: 👇

Texto alternativo

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, escribiremos este:

//👇 the magical keyword
 async function order() {
    // Write code here
 }

Pero espera……

Texto alternativo

Necesitas entender ->

  • Cómo utilizar tryycatch
  • Cómo usar await

Cómo usar Try and Catch

Usamos try para ejecutar nuestro código mientras que usamos catchpara 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.

Promesas en JS -> resolver o rechazar

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]

Async / Await en JS -> probar, capturar

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.

Cómo usar Await de JavaScript

Texto alternativo

La palabra clave awaithace que JavaScript espere hasta que se establezca una promesa y devuelva su resultado.

Cómo usar await en JavaScript

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:

  • lavar los platos
  • limpiar las mesas
  • recibir pedidos, etc.

Ejemplo de código await

Texto alternativo

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:

Texto alternativo

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.

Nota

Al usar Async/Await, también puedes usar los controladores .then.catchy , .finally  que son una parte central de las promesas.

Abrimos de nuevo nuestra heladería

Texto alternativo

Vamos a crear dos funciones ->

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

Texto alternativo

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

Texto alternativo

Hasta aquí todo bien.

Texto alternativo

Completemos nuestro proyecto.

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

Texto alternativo
La tabla contiene los pasos para hacer helado

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

Texto alternativo

Conclusión

¡Felicidades 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 (devoluciones de llamada, promesas y Async/Await)

Aquí está tu medalla por leer hasta el final. ❤️

Se agradecen sugerencias y críticas ❤️

Texto alternativo

YouTube / Alegría Shaheb

LinkedIn / JoyShaheb

Twitter / Joy Shaheb

Instagram / JoyShaheb

Créditos

Valora este artículo