# Funciones asíncronas

## Resumen

La mayor parte del código en tu escena se ejecuta de forma síncrona usando un solo hilo. Eso significa que los comandos se ejecutan secuencialmente línea por línea. Cada comando debe esperar a que el comando anterior termine de ejecutarse antes de poder comenzar.

Incluso las funciones en los systems de tu escena se ejecutan una por una, siguiendo un [orden de prioridad](https://docs.decentraland.org/creator/content-creator-es/arquitectura/systems#system-execution-order).

Ejecutar código de forma síncrona asegura consistencia, ya que siempre puedes estar seguro del orden en el que se ejecutan los comandos de tu código.

Por otro lado, tu escena necesita actualizarse muchas veces por segundo, construyendo el siguiente frame. Si una parte de tu código tarda demasiado en responder, entonces todo el hilo principal queda bloqueado y esto resulta en tasas de frames con lag.

Por eso, en algunos casos quieres que ciertos comandos se ejecuten de forma asíncrona. Esto significa que puedes iniciar una tarea en un nuevo hilo, y mientras tanto el hilo principal puede seguir ejecutando las siguientes líneas de código.

Esto es especialmente útil para tareas que dependen de servicios externos que podrían tardar en responder, ya que no quieres que ese tiempo inactivo esperando la respuesta bloquee otras tareas.

Por ejemplo:

* Al recuperar datos de una API REST
* Al realizar una transacción en la blockchain

{% hint style="warning" %}
**📔 Nota**: Ten en cuenta que varios frames de tu escena podrían renderizarse antes de que la tarea termine de ejecutarse. Asegúrate de que el código de tu escena sea lo suficientemente flexible para manejar los escenarios intermedios mientras la tarea asíncrona se completa.
{% endhint %}

## Ejecutar una función async

Marca cualquier función como `async` para que se ejecute en un hilo separado del hilo principal de la escena cada vez que se llame.

```ts
// declarar función async
async function myAsyncTask() {
	// ejecutar los pasos de la función
}

// llamar a la función async
myAsyncTask()

// el resto del código sigue ejecutándose
```

## La función executeTask

La `executeTask()` función ejecuta una función lambda de forma asíncrona, en un hilo separado del hilo principal de la escena. `executeTask()` nos permite declarar y ejecutar la función todo en una misma sentencia.

```ts
executeTask(async () => {
	let data = await myAsyncTask()
	console.log(data)
})

// el resto del código sigue ejecutándose
```

## La función then

La `then` la función recibe una función lambda como argumento, que solo se ejecuta una vez que la sentencia previa ha terminado. Esta función lambda puede opcionalmente tener entradas que se mapean a lo que la sentencia previa retorna.

```ts
myAsyncTask().then((data) => {
	console.log(data)
})
```

{% hint style="warning" %}
**📔 Nota**: Generalmente es mejor usar `executeTask` enfoque en lugar de la `then` función. En este ejemplo, la escena no será considerada completamente cargada por el Explorer hasta que la `myAsyncTask()` función se complete, lo que puede afectar los tiempos de carga. Además, si dependes demasiado de la `then` función en múltiples niveles anidados, puedes terminar con lo que se conoce como "callback hell", donde el código puede volverse muy difícil de leer y mantener.
{% endhint %}

## Funciones PointerEvents y RayCast

Cuando tu escena usa un `PointerEvent` o un `RayCast` componente, los cálculos de colisiones se realizan de forma async en el Engine. Luego el Engine retorna un evento de resultados a la escena, que puede llegar uno o varios ticks del game loop después de que el evento fue invocado.

Entonces necesitas crear un system para procesar estos resultados en el frame en que llegan.

{% hint style="warning" %}
**📔 Nota**: Si manejas clicks vía el [**Registrar un callback**](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/interactividad/eventos-de-button/register-callback) enfoque, no necesitas crear explícitamente un system para manejar esto, pero lo mismo ocurre en segundo plano.
{% endhint %}

Ver [eventos de click](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/interactividad/eventos-de-button/click-events) y [raycasting](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/interactividad/raycasting).

{% hint style="info" %}
**💡 Consejo**: Si el procesamiento de los resultados de un raycast requiere muchos cálculos (como ejecutar un algoritmo de búsqueda de caminos) podrías querer ejecutar esa computación en una función asíncrona.
{% endhint %}

## La sentencia await

Una `await` sentencia fuerza la ejecución a esperar una respuesta antes de pasar a la siguiente línea de código. `await` las sentencias solo pueden usarse dentro de un bloque de código async.

```ts
// declarar función
async function myAsyncTask() {
	try {
		let response = await fetch(callUrl)
		let json = await response.json()
		console.log(json)
	} catch {
		console.log('no se pudo alcanzar la URL')
	}
}

// llamar a la función
myAsyncTask()

// El resto del código sigue ejecutándose
```

El ejemplo anterior ejecuta una función que incluye una `fetch()` operación para recuperar datos de una API externa. La `fetch()` operación es asíncrona, ya que no podemos predecir cuánto tardará el servidor en responder. Sin embargo, la siguiente línea necesita la salida de esta operación para poder parsearla como json. La `await` sentencia aquí asegura que la siguiente línea solo se ejecutará una vez que esa operación haya devuelto un valor. De manera similar, la `response.json()` función también es asíncrona, pero la siguiente línea necesita que el json esté parseado antes de poder registrarlo. La segunda `await` sentencia fuerza que la siguiente línea solo se llame una vez que el parseo del json haya terminado, por mucho tiempo que tome.

## Establecer un Timeout para la llamada a una función

Usa `setTimeout` para esperar un tiempo antes de que se ejecuten ciertas líneas de código. Esto toma dos argumentos:

* La función a ejecutar
* La cantidad de milisegundos a esperar antes de ejecutar esa función

El ejemplo a continuación espera 1000 milisegundos (equivalente a 1 segundo) antes de ejecutar una función simple que registra un mensaje en la consola.

```ts
import { timers } from '@dcl/sdk/ecs'

console.log('Esto se imprime de inmediato')

timers.setTimeout(() => {
	// función para ejecutar después del retraso
    console.log('Esto se imprime después de 1 segundo')
}, 1000)
```

La `clearTimeout` puede usarse para cancelar la ejecución de una `setTimeout` función que aún está esperando ser ejecutada. Al crear la `setTimeout` función, obtén una referencia a la función, que luego puedes pasar a `clearTimeout`. En este caso, la variable `intervalId` se obtiene al hacer el `setTimeout`, y luego se pasa a `clearTimeout` para cancelarla.

```ts
import { timers } from '@dcl/sdk/ecs'

const timeoutId = timers.setTimeout(() => {
    console.log('Espera 1 segundo antes de ejecutar esta función')
}, 1000)

timers.clearTimeout(intervalId)
```
