109 / Avanzado

await en el navegador

Nyx compilado a wasm32-wasi puede usar await de verdad: una async fn que llama a await browser_fetch_await(...) ESPERA la respuesta del servidor y sigue en la línea siguiente con el valor, sin partir el flujo en un cierre por cada pedido. El módulo nuevo std/browser_await vive aparte de std/browser a propósito: solo paga el costo de Asyncify (más binaryen como dependencia de build) el programa que lo importa.

109-await-fetch-wasm.nxFuente →
// 109 — await en el navegador: una pantalla que carga una factura sin callbacks
//
// Target wasm32-wasi (corre en el navegador, o bajo node con
// examples/browser/run-node.mjs). `await browser_fetch_await(...)` ESPERA la
// respuesta del servidor y la función sigue en la línea siguiente con el
// valor: sin partir el flujo en un cierre por cada pedido.
//
// Construir:  nyx build --target wasm32-wasi
//   (necesita binaryen: `sudo apt install binaryen`, o NYX_WASM_OPT=/ruta/a/wasm-opt;
//    nyx build lo usa SOLO porque el programa espera al anfitrión)
//
// Lo que conviene saber:
//   - El status HTTP no es error: un 404 llega como Ok con r.status == 404.
//     Err es «no hubo respuesta» (kind "connection") o «venció el plazo» ("timeout").
//   - UNA sola función puede estar esperando a la vez. Un click que llega
//     durante el fetch se procesa cuando el fetch termina, en orden.
//   - spawn y los canales siguen sin existir en wasm: await es secuencial.

import "std/browser_await"
import "std/dom"
import "std/error"

async fn cargar_factura(id: int) -> String {
    let res = await browser_fetch_await_opts("/api/facturas/" + int_to_string(id), "GET", "", 5000)
    match res {
        Result.Ok(r) => {
            if r.status != 200 {
                return "la factura " + int_to_string(id) + " no está (HTTP " + int_to_string(r.status) + ")"
            }
            return r.body
        }
        Result.Err(e) => { return "sin respuesta del servidor: " + e.kind + " (" + e.msg + ")" }
    }
}

// El botón «abrir» de cada fila llama a este export con el número de factura.
#[export_name = "abrir_factura"]
fn abrir_factura(id: int) {
    dom_set_text("#estado", "cargando la factura " + int_to_string(id) + "...")
    let texto: String = await cargar_factura(id)
    dom_set_text("#factura", texto)
    dom_set_text("#estado", "listo")
}

fn main() {
    abrir_factura(7)
}
Salida ilustrativastdout
#estado → "cargando la factura 7..."
#factura → (el cuerpo de la respuesta, o el motivo del fallo)
#estado → "listo"

Cómo funciona

El status HTTP no es un error: un 404 llega como Result.Ok con r.status == 404, y cargar_factura lo distingue a mano. Lo único que cae en Result.Err es no haber tenido respuesta —kind "connection" o "timeout"—, el mismo vocabulario de Error que el resto de la stdlib.

abrir_factura es el export que llama el botón «abrir» de cada fila: actualiza el DOM antes de esperar, espera con await, y actualiza el DOM otra vez con el resultado — sin un callback de por medio. Solo puede haber UNA función esperando a la vez: un click que llega durante el fetch se procesa recién cuando el fetch termina, en orden.

Construir necesita binaryen (wasm-opt) porque el .ll generado trae un marcador de imports Asyncify; un programa sin #[suspends] compila byte a byte igual que antes. spawn y los canales siguen sin existir en wasm: acá await es secuencial.