Async Wasm

28.1 The Problem

C/C++/Rust code is often synchronous (blocking file reads, sleep, synchronous network calls), but browsers expose asynchronous APIs (Promises). Wasm execution can't block the main thread.

28.2 Asyncify (Binaryen/Emscripten)

A compile-time transform that lets Wasm code unwind and rewind its stack, so it can pause while JS awaits a Promise, then resume.

Costs: larger code and slower execution (instrumentation). Limit it with -sASYNCIFY_ONLY / ASYNCIFY_IMPORTS.

#include <emscripten.h>
EM_ASYNC_JS(int, fetch_length, (const char* url), {
  const res = await fetch(UTF8ToString(url));
  return (await res.text()).length;
});
int main() {
    int len = fetch_length("/data.txt");   // looks synchronous in C
    emscripten_sleep(100);                 // also uses Asyncify
    return len;
}
emcc app.c -O3 -sASYNCIFY -o app.js

28.3 JS Promise Integration (JSPI)

A standardized engine feature (in Chrome 137+, other browsers following): wrap Wasm exports with WebAssembly.promising and imports with WebAssembly.Suspending. Wasm suspends on a Promise without code transformation.

Emscripten: -sJSPI. Much smaller and faster than Asyncify.

const suspendingFetch = new WebAssembly.Suspending(async (ptr, len) => {
  const res = await fetch(readString(ptr, len));
  return (await res.arrayBuffer()).byteLength;
});
const { instance } = await WebAssembly.instantiate(bytes, { env: { fetch_len: suspendingFetch } });
const run = WebAssembly.promising(instance.exports.run);
const result = await run();   // returns a Promise