Skip to content

Instantly share code, notes, and snippets.

@jeremy-code
Created April 20, 2025 22:48
Show Gist options
  • Save jeremy-code/ac49a50f300a7290ea6473ff1fc50a8f to your computer and use it in GitHub Desktop.
Save jeremy-code/ac49a50f300a7290ea6473ff1fc50a8f to your computer and use it in GitHub Desktop.
JSPI Emscripten C

JavaScript Promise Integration (JSPI) is a Phase 4 (Standardize the Feature) WebAssembly proposal for running asynchronous Web APIs alongside synchronous code.

JSPI is supported under the following browsers/engines through enabling a feature flag:

Chrome: chrome://flags/#enable-experimental-webassembly-jspi

Firefox: javascript.options.wasm_js_promise_integration

Node.js: --experimental-wasm-jspi

Deno: --v8-flags=--experimental-wasm-jspi

For a quick demo, let's write some async code in C with Emscripten.

Run this command in Docker to get an Emscripten shell:

docker run \
  --interactive \
  --rm \
  --tty \
  --volume "./:/src/" \
  --workdir /src/ \
  emscripten/emsdk \
  /bin/bash

Consider the following code:

// main.c
#include <stdio.h>
#include <emscripten.h>

EM_ASYNC_JS(void, readFile, (const char *path), {
  const fsPromises = await import('fs/promises');
  const data = await fsPromises.readFile(UTF8ToString(path));

  console.log(`File contents: ${data}`);
});

int EMSCRIPTEN_KEEPALIVE main() {
  printf("Hello, WebAssembly!\n");
  readFile("./password.txt");
  printf("File read request sent.\n");
  return 0;
}

Compile it:

emcc \
  -sENVIRONMENT=node \
  -sMODULARIZE=1 \
  -sJSPI=1 \
  -o ./output/module.js \
  ./main.c

and run it with node --experimental-wasm-jspi index.js:

// index.js
import module from "./output/module.js";

const myModule = await module();
Hello, WebAssembly!
File contents: Hunter2

File read request sent.

This code isn't particularly useful though, we could already do this without C. Suppose instead of just printing to the console, we actually want it to set a buffer?

// main.c
#include <stdio.h>
#include <emscripten.h>

EM_ASYNC_JS(void, readFile, (const char *path, void *buffer), {
  // If you don't believe this is async, uncomment the next line
  // await new Promise((resolve) => setTimeout(resolve, 5000));

  const fsPromises = await import('fs/promises');
  const data = await fsPromises.readFile(UTF8ToString(path));
  const bufferToFill = _malloc(data.length * data.BYTES_PER_ELEMENT);
  HEAPU8.set(data, bufferToFill);
  setValue(buffer, bufferToFill, "*");
});

int EMSCRIPTEN_KEEPALIVE main() {
  void *buffer = NULL;
  printf("Hello, WebAssembly!\n");
  readFile("./password.txt", &buffer);
  printf("File buffer pointer: %p\n", buffer);

  char* currChar = (char *)buffer;

  while (*currChar != '\0') {
    printf("%c", *currChar);
    currChar++;
  }
  printf("*currChar is NULL: %d\n", *currChar == '\0');

  free(buffer);
  return 0;
}

And run the previous compile command with -sEXPORTED_FUNCTIONS='["_malloc", "_main"]' added:

Hello, WebAssembly!
File buffer pointer: 0x11488
Hunter2
*currChar is NULL: 1

Now, this specific file is pretty dumb but the general gist of it is: you can run asynchronous JavaScript code in synchoronous C code. This is a big gamechanger for bringing in common async use cases (fetching data, filesystem etc.) with C libraries.

One MAJOR caveat is: any functions that you can expect to run any async function (in this case, main() and readFile) must be added to sJSPI_EXPORTS= if it isn't obvious (e.g. in this case, since we used the EM_ASYNC_JS macro, it isn't necessary). Otherwise, you will get the error RuntimeError: attempting to suspend without a WebAssembly.promising export (add -g2 for the error stack to have reasonable function names, and add all the function names up to that async function).

The reason this is I imagine is that unlike Asyncify, Emscripten can't automatically instrument function as async or not. I couldn't find any issues on this, and I've had trouble replicating it in a more easily reproducible form.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment