JSPI Explicado: Uma Ponte Melhor Entre JavaScript e Wasm
JSPI liga JavaScript e WebAssembly para que Wasm síncrono chame APIs baseadas em Promise como fetch, com Suspending, promising e status nos navegadores.
A Integração de Promises JavaScript (JSPI) permite que um módulo WebAssembly chame uma importação JavaScript que retorna uma Promise como se fosse uma função síncrona: o módulo suspende quando a importação retorna uma Promise e retoma com o valor resolvido, sem necessidade de gerenciamento manual de callbacks. Essa única capacidade preenche uma lacuna de longa data — código Wasm síncrono compilado a partir de C, C++ ou Rust não conseguia fazer await de uma API de navegador assíncrona como fetch ou IndexedDB sem ferramentas pesadas. Este artigo aborda o problema que o JSPI resolve, a API atual de duas funções, um exemplo funcional com fetch e onde ele está disponível em 2026. Um aviso importante: a maioria dos tutoriais de JSPI ainda mostra uma API com objeto Suspender que foi removida — tudo abaixo utiliza a superfície atual.
Principais Conclusões
- A API pública do JSPI é composta exatamente por duas partes:
new WebAssembly.Suspending(fn)marca uma importação que retorna uma Promise, eWebAssembly.promising(exportFn)envolve uma função Wasm exportada para que sua chamada retorne uma Promise. - Faça a detecção de suporte com
'Suspending' in WebAssembly— nunca com'Suspender' in WebAssembly, que verifica a API anterior removida antes de 2024. - Em meados de 2026, o JSPI é uma proposta na fase 4 (efetivamente padronizada), disponível no Chrome 137+, no Safari 27 beta e no Firefox apenas no canal Nightly com uma preferência, com ativação padrão prevista para o Firefox 153.
- Se a Promise importada for rejeitada, o JSPI lança uma exceção na computação suspensa em vez de retornar um valor de erro ao Wasm.
- Ao contrário do Asyncify do Binaryen, o JSPI utiliza troca de pilha nativa do motor, portanto o binário mantém seu código síncrono linear sem sobrecarga de instrumentação.
A ponte problemática: Wasm síncrono encontra a web assíncrona
A incompatibilidade é arquitetural. O WebAssembly compilado a partir de C, C++ ou Rust pressupõe chamadas bloqueantes — uma função chama outra, aguarda o valor de retorno e continua. A plataforma web é o oposto: fetch, IndexedDB e a maioria das APIs modernas de navegador retornam Promises e resolvem posteriormente, conduzidas pelo loop de eventos. Quando o Wasm chama uma função JavaScript que retorna uma Promise, o módulo não tem como pausar nativamente, aguardar a resolução e retomar de onde parou.
Antes do JSPI, a solução padrão era o Asyncify do Binaryen, uma transformação de programa completo que reescreve o binário Wasm para que ele possa desenrolar sua própria pilha na memória linear e rebobiná-la posteriormente. Funciona, mas o custo é real: a transformação infla o tamanho do binário e adiciona sobrecarga por chamada às funções instrumentadas. Para uma rotina de computação de alto desempenho que ocasionalmente precisa fazer fetch de configurações ou ler do IndexedDB durante a computação, pagar esse custo em todo o módulo é um mau negócio.
O que a Integração de Promises JavaScript faz
Discover how at OpenReplay.com.
A Integração de Promises JavaScript conecta o WebAssembly síncrono às APIs Web assíncronas mapeando uma chamada Wasm síncrona em uma assíncrona: suspende o módulo quando uma importação com Promise é chamada e o retoma quando a Promise é resolvida. Ela permite que a aplicação WebAssembly invoque as chamadas importações com Promise e acesse o valor da Promise, sem precisar gerenciar explicitamente os callbacks assíncronos normalmente associados às Promises.
É importante ressaltar que isso não é uma mudança de linguagem. A proposta não faz alterações na linguagem JavaScript nem na linguagem WebAssembly. Não há novas instruções ou tipos WebAssembly especificados. Semanticamente, todas as mudanças descritas estão na fronteira entre WebAssembly e JavaScript. Esse enquadramento de fronteira é importante para o design da API e para como a suspensão é delimitada.
A API de duas partes e um exemplo funcional com fetch
Toda a superfície pública do JSPI é composta por dois elementos. Existem dois elementos na API do JSPI: o construtor WebAssembly.Suspending e a função WebAssembly.promising. new WebAssembly.Suspending(fn) marca uma importação que retorna uma Promise; a função WebAssembly.promising é usada para envolver uma função WebAssembly exportada em uma que retorna uma Promise. Observe a capitalização — Suspending é um construtor (com maiúscula), promising é uma função (com minúscula).
A seguir está a estrutura canônica adaptada do exemplo da especificação: uma importação baseada em fetch envolvida com Suspending, uma exportação envolvida com promising e a Promise resultante aguardada a partir do JavaScript.
// An async import that returns a Promise resolving to a number.
const computeDelta = () =>
fetch('https://example.com/data.txt')
.then(res => res.text())
.then(txt => parseFloat(txt));
const importObject = {
js: {
// Mark the Promise-returning import as suspending.
compute_delta: new WebAssembly.Suspending(computeDelta),
},
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch('module.wasm'),
importObject,
);
// Wrap the export so calling it returns a Promise.
const updateState = WebAssembly.promising(instance.exports.update_state);
const result = await updateState(); // suspends inside Wasm on compute_delta, resumes with the value
Dentro de update_state, o código Wasm chama compute_delta com uma assinatura de chamada síncrona comum. Quando essa importação retorna uma Promise, o módulo suspende; quando a Promise é resolvida, o valor resolvido torna-se o valor de retorno da importação e a execução continua.
Comportamentos importantes de conhecer
Três detalhes distinguem o JSPI na prática do modelo mental ingênuo.
A suspensão é delimitada pela fronteira JS/Wasm. A importação Suspending e a exportação promising formam um par — a chamada mais interna para uma exportação envolvida determina o ponto de corte para o que é suspenso. Apenas computações WebAssembly podem ser suspensas usando JSPI; isso é garantido exigindo que apenas frames WebAssembly estejam ativos entre a chamada a uma função promising e qualquer chamada a uma importação envolvida com Suspending.
A suspensão só ocorre se uma Promise for realmente retornada. Em vez de sempre suspender ao chamar uma função JavaScript a partir de uma importação suspending, suspendemos apenas quando a função JavaScript realmente retorna uma Promise. Um valor de retorno simples é passado diretamente sem nenhuma ida ao loop de eventos.
Uma Promise rejeitada lança uma exceção no Wasm. Se a Promise for rejeitada, em vez de retomar o módulo WebAssembly com o valor, uma exceção é propagada para a computação suspensa. Na prática, a rejeição geralmente é tratada no lado do JavaScript, pois uma linguagem como Rust frequentemente não consegue agir sobre essa exceção diretamente — o projeto wasm-bindgen discutiu a adição de um tipo explícito indicador de erro, uma discussão em aberto em vez de uma API definida.
Status em navegadores e toolchains (2026)
O JSPI atingiu a fase 4 do processo WebAssembly do W3C — está na fase 4 do W3C WebAssembly WG, o que significa que a especificação foi votada pelo W3C Wasm CG — está efetivamente padronizado. Esta especificação foi padronizada pelo W3C WebAssembly CG em abril de 2025.
| Ambiente | Status (meados de 2026) |
|---|---|
| Chrome / Edge | Disponível em versão estável desde o Chrome 137 (maio de 2025) |
| Safari | Disponível no Safari 27 beta |
| Firefox | Apenas no canal Nightly com preferência; ativação padrão prevista para o Firefox 153 |
| Node.js | Disponível com --experimental-wasm-jspi |
Para o Firefox, utilize o status oficial da Mozilla em vez do número “Firefox 139” que circula por aí: de acordo com o Intent to Ship (10 de junho de 2026), este recurso foi desenvolvido e disponibilizado com uma preferência, ativado apenas no canal Nightly desde o Fx152. A Mozilla pretende ativar a Integração de Promises JavaScript do WebAssembly (JSPI) por padrão em todas as plataformas a partir do Firefox 153. No momento da redação deste artigo, o caniuse ainda lista o Firefox estável como não ativado por padrão, portanto confirme antes de depender disso.
Em relação às toolchains, a maioria dos projetos C/C++ não precisa de alterações no código-fonte. Se você usa Emscripten, a adoção da nova API geralmente não exigirá alterações no seu código. Você deve estar usando uma versão do Emscripten de pelo menos 3.1.61. Detecte o suporte de forma limpa:
if ('Suspending' in WebAssembly) {
// JSPI is available — wire up Suspending / promising
} else {
// fall back to an Asyncify-built module
}
Verifique WebAssembly.Suspending, não Suspender: a API antiga continuará operando pelo menos até 29 de outubro de 2024 (Chrome M128). Após isso, planejamos remover a API antiga. Observe que o próprio Emscripten não suportará mais a API antiga a partir da versão 3.1.61. Uma API anterior com objeto Suspender existiu e foi removida — se um tutorial mostrar WebAssembly.Suspender ou new WebAssembly.Function(...) com returnPromiseOnSuspend, ele está desatualizado.
JSPI vs Asyncify, brevemente
A diferença decisiva está em onde reside a lógica de suspensão. O Asyncify a coloca no seu binário; o JSPI a coloca no motor. Como os mecanismos utilizados ao suspender e retomar módulos WebAssembly são essencialmente de tempo constante, não antecipamos custos elevados no uso do JSPI — especialmente em comparação com outras abordagens baseadas em transformação. Isso significa saída menor e menor sobrecarga por chamada, com troca de pilha nativa em vez de uma reescrita de programa completo. A implementação atual aloca pilhas de tamanho fixo por computação suspensa; pilhas crescentes (segmentadas) estão no roadmap para suportar um grande número de corrotinas, mas ainda não foram disponibilizadas.
Se você compila para Wasm e chegou ao ponto em que código síncrono precisa de uma API Web assíncrona, o JSPI é a resposta atual: envolva a importação em WebAssembly.Suspending, envolva a exportação em WebAssembly.promising, faça a verificação com 'Suspending' in WebAssembly e mantenha o Asyncify apenas como fallback para motores que ainda não o implementaram.
Perguntas Frequentes
Qual é a diferença entre JSPI e Asyncify?
O Asyncify é uma transformação Binaryen de programa completo que reescreve todo o binário Wasm para desenrolar e rebobinar sua própria pilha na memória linear, inflando o tamanho do binário e adicionando sobrecarga por chamada às funções instrumentadas. O JSPI move essa lógica para o motor usando troca de pilha nativa, de modo que o módulo mantém código síncrono linear sem instrumentação. O V8 descreve os mecanismos de suspensão e retomada do JSPI como essencialmente de tempo constante, enquanto o Asyncify onera todo o módulo.
Preciso alterar meu código-fonte C ou C++ do Emscripten para usar o JSPI?
Não. O Emscripten emite a API atual do JSPI automaticamente a partir da versão 3.1.61, portanto a maioria dos projetos C e C++ não precisa de alterações no código-fonte para migrar da antiga API com objeto Suspender para a nova superfície com Suspending e promising. Você só precisa compilar com Emscripten 3.1.61 ou posterior; a API anterior a 2024 foi removida do Emscripten nessa mesma versão, portanto toolchains mais antigas ainda emitem a superfície removida.
O que acontece se a Promise importada for rejeitada?
Uma Promise rejeitada não retorna um valor de erro ao Wasm; em vez disso, o JSPI propaga uma exceção para a computação suspensa. Na prática, a rejeição é tipicamente tratada no lado do JavaScript, pois uma linguagem como Rust frequentemente não consegue agir sobre essa exceção lançada diretamente. A assinatura da importação Wasm pode reportar um inteiro simples mesmo que represente uma Promise, e o wasm-bindgen tem uma discussão em aberto sobre a adição de um tipo explícito indicador de erro em vez de uma API definida.
Chamar uma importação JSPI sempre suspende o módulo?
Não. O JSPI só suspende quando a importação JavaScript realmente retorna uma Promise. Se a função importada retornar um valor síncrono simples, o resultado é passado diretamente ao chamador Wasm sem suspensão e sem ida ao loop de eventos. Esse comportamento é definido na fronteira entre JavaScript e WebAssembly, portanto a mesma importação envolvida pode se comportar de forma síncrona ou assíncrona dependendo do que a função subjacente retorna em tempo de execução.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k