WebAssembly JSPI Gets a Streamlined API: Key Changes and How to Adapt
By
<p>The JavaScript Promise Integration (JSPI) API for WebAssembly has received a significant update, now shipping in Chrome release M126. This overhaul simplifies the developer experience, removes outdated constructs, and introduces more intuitive behavior. In this article, we break down what has changed, how to use the new API with Emscripten, and what lies ahead on the JSPI roadmap.</p>
<h2 id="understanding-jspi">Understanding JSPI and Its Purpose</h2>
<h3 id="what-is-jspi">What Is JSPI?</h3>
<p>JSPI bridges the gap between synchronous WebAssembly applications, often compiled from C or C++, and the asynchronous nature of many modern Web APIs. These APIs return JavaScript Promises rather than blocking until completion. JSPI allows a WebAssembly module to pause execution when a Promise is encountered and resume once that Promise resolves—all without requiring changes to the original synchronous code.</p><figure style="margin:20px 0"><img src="https://picsum.photos/seed/2548429991/800/450" alt="WebAssembly JSPI Gets a Streamlined API: Key Changes and How to Adapt" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px"></figcaption></figure>
<h2 id="whats-new">What's New in the JSPI API?</h2>
<h3 id="eliminating-suspender">Eliminating the Suspender Object</h3>
<p>In January 2024, the Stacks subgroup of the WebAssembly Community Group voted to revise the JSPI API. The most notable change is the removal of the explicit <strong>Suspender</strong> object. Previously, developers had to manually create and manage Suspender instances to define which parts of a computation could be suspended. Now, the JavaScript/WebAssembly boundary itself serves as the delimiter. The most recent call into a wrapped WebAssembly export determines the cut point for suspension.</p>
<p>This adjustment gives developers slightly less granular control over suspension boundaries, but the trade-off is a significantly simpler API. No longer do you need to track Suspender objects or worry about their lifecycle.</p>
<h3 id="no-more-webassembly-function">No More WebAssembly.Function</h3>
<p>Another key change is the departure from using the <code>WebAssembly.Function</code> constructor to create JSPI wrappers. Instead, the API now provides dedicated functions and constructors tailored specifically for JSPI. This shift offers several benefits:</p>
<ul>
<li>It eliminates the dependency on the Type Reflection Proposal, which was required for the old approach.</li>
<li>It simplifies tooling—the new API functions no longer need to explicitly reference WebAssembly type information.</li>
</ul>
<p>This simplification was made possible by the removal of the Suspender object, which previously required type-level annotations.</p>
<h3 id="smarter-suspension-behavior">Smarter Suspension Behavior</h3>
<p>The third major change relates to when suspension actually occurs. In the previous API, calling any JavaScript function from a suspending import would always trigger a suspension. Now, suspension only happens when the called JavaScript function returns a Promise. If the function completes synchronously, no suspension occurs.</p>
<p>While this behavior might seem to contradict the recommendations of the W3C TAG, it represents a safe and beneficial optimization. Since JSPI acts as the caller of the function that returns a Promise, it can intelligently decide whether to suspend. Most applications will see minimal impact, but those that frequently call synchronous JavaScript functions will notice improved performance by avoiding unnecessary trips to the browser's event loop.</p>
<h2 id="the-updated-api">The Updated API in Practice</h2>
<p>The revamped API is straightforward. You create a JSPI wrapper by calling a function that takes an exported WebAssembly function and returns a new function that returns a JavaScript Promise. Here’s a conceptual example:</p>
<pre><code>// Old way (deprecated)
const wrappedFunc = new WebAssembly.Function({parameters: [], results: []}, suspendingFunc);
// New way
const wrappedFunc = jsPromiseIntegration.wrap(wasmInstance.exports.someFunction);
</code></pre>
<p>When using <a href="https://emscripten.org/">Emscripten</a>, the compiler now automatically handles these wrappers for many common patterns. You can rely on Emscripten’s <code>-sASYNCIFY</code> flag to manage the integration seamlessly. However, if you need manual control, the new API is accessible through the global <code>WebAssembly.JSPI</code> object (or similar, depending on the implementation).</p>
<h2 id="roadmap-and-migration">Looking Ahead: Roadmap and Migration</h2>
<p>The JSPI specification continues to evolve. The Chrome team at Google has indicated that future releases will further stabilize the API, with a focus on performance and broader browser support. Developers currently using the old API should plan to migrate to the new design as soon as possible. The changes are backward-incompatible, but the migration path is clear:</p>
<ol>
<li>Remove any manual <code>Suspender</code> object creation and usage.</li>
<li>Replace <code>WebAssembly.Function</code> wrappers with the new JSPI-specific functions.</li>
<li>Test your application to ensure suspension behavior matches expectations—especially if you relied on the old eager-suspension model.</li>
</ol>
<p>For more detailed information, refer to the <a href="https://github.com/WebAssembly/js-promise-integration">JSPI specification</a> and the <a href="https://developer.chrome.com/blog/webassembly-jspi-new-api/">original Chrome announcement</a>.</p>
<p>By embracing these changes, developers can build more efficient and maintainable WebAssembly applications that seamlessly interact with the asynchronous web platform.</p>