La asincronía permite suspender una tarea mientras el runtime continúa atendiendo otras operaciones. En DeviceScript se expresa principalmente con async, await y funciones que devuelven promesas.
Cuando en Arduino escribes delay(1000), el microcontrolador se “congela”. Se queda en esa línea contando milisegundos. Si aprietas un botón durante ese segundo, el Arduino no se entera. Si tenía que actualizar una pantalla, no lo hace.
Para solucionar esto en C++, teníamos que recurrir al famoso “Blink Without Delay” y pelearnos con la función millis(), restando tiempos y creando máquinas de estados. Funciona, pero el código se vuelve sucio y difícil de leer rápidamente.
En DeviceScript podemos pausar una función sin detener el bucle de eventos, permitiendo que el microcontrolador avance con otras tareas. Esto es concurrencia cooperativa, no ejecución simultánea en varios núcleos.
El problema del código bloqueante
Pensemos en un cocinero.
- Modelo bloqueante: Pones el pan en la tostadora y te quedas delante sin hacer nada hasta que termina.
- Modelo asíncrono: Pones el pan en la tostadora y aprovechas la espera para hacer otras tareas. Cuando termina, vuelves a recogerlo.
DeviceScript utiliza este segundo modelo gracias al Event Loop (Bucle de Eventos).
Las palabras clave async y await
Para gestionar este flujo, TypeScript nos da dos palabras clave fundamentales: async y await.
async
Cuando definimos una función con async, le estamos diciendo al sistema: “Oye, esta función puede tardar un rato en completarse y puede que tenga pausas en su interior. No te bloquees esperándola”
await
Esta es la clave. await se usa dentro de funciones async. Significa: “Pausa la ejecución DE ESTA FUNCIÓN aquí hasta que la tarea termine, pero mientras tanto, deja que el procesador haga otras cosas”.
Esperar con delay
En DeviceScript, la función delay se importa desde @devicescript/core. A diferencia del de Arduino, este delay devuelve una Promesa que se resuelve tras el tiempo indicado.
Mirad la diferencia:
import { delay } from "@devicescript/core"
import { startLightBulb } from "@devicescript/servers"
import { gpio } from "@devicescript/core"
const led = startLightBulb({ pin: gpio(2) })
// Definimos una función asíncrona
async function parpadear() {
while(true) {
await led.write(1)
// Aquí pausamos 1 seg, PERO el procesador queda libre
await delay(1000)
await led.write(0)
await delay(1000)
}
}
// Arrancamos la tarea
parpadear()Cuando el código llega a await delay(1000), la función parpadear se “duerme”. Pero el sistema DeviceScript sigue despierto: puede escuchar botones, recibir datos WiFi o ejecutar otras funciones.
Coordinar varias tareas
Vamos a hacer que dos LED parpadeen a ritmos distintos sin mezclar sus temporizadores en un único bucle.
En C++ tendríamos que usar dos variables unsigned long previousMillis, dos intervals, y mucha lógica en el loop().
En DeviceScript, simplemente lanzamos dos funciones asíncronas:
import { delay } from "@devicescript/core"
import { startLightBulb } from "@devicescript/servers"
import { pins } from "@dsboard/esp32_c3_devkit_m" // Ajusta a tu placa
// Definimos dos luces virtuales o reales
const ledRapido = startLightBulb({ pin: pins.GPIO2, roleName: "led1" })
const ledLento = startLightBulb({ pin: pins.GPIO3, roleName: "led2" })
// Tarea 1: Parpadea cada 200ms
async function tareaRapida() {
while (true) {
await ledRapido.intensity.write(1)
await delay(200)
await ledRapido.intensity.write(0)
await delay(200)
}
}
// Tarea 2: Parpadea cada 1000ms
async function tareaLenta() {
while (true) {
await ledLento.intensity.write(1)
await delay(1000) // 1 segundo
await ledLento.intensity.write(0)
await delay(1000)
}
}
// Lanzamos ambas "a la vez"
tareaRapida()
tareaLenta()
console.log("Ambas tareas están corriendo concurrentemente")En el simulador aparecerán dos bombillas con ritmos distintos. Cada tarea conserva su propia secuencia, aunque ambas comparten el mismo runtime.
No hemos necesitado restar marcas de tiempo. Hemos descrito cada comportamiento de forma lineal y el runtime intercala las tareas mediante concurrencia cooperativa.
Errores comunes
El error más habitual al empezar es olvidarse de await.
// ❌ INCORRECTO
async function malEjemplo() {
console.log("Inicio")
delay(5000) // Falta el await!
console.log("Fin")
}Al ejecutar el ejemplo, Inicio y Fin aparecen sin esperar cinco segundos.
Al omitir await, iniciamos el temporizador pero no suspendemos la función actual. Por eso el código continúa inmediatamente con la siguiente línea.
Si una función devuelve una promesa (como delay o muchas operaciones read y write), usa await cuando necesites su resultado antes de continuar.