发布时间June 4, 2024·标记为WebAssembly
WebAssembly’s JavaScript Promise Integration (JSPI) API has a new API, available in Chrome release M126. We talk about what has changed, how to use it with Emscripten, and what is the roadmap for JSPI.
WebAssembly的JavaScript Promise Integration(JSPI)API有一个新的API,可在Chrome版本M126中使用。我们讨论了哪些变化,如何将其与Emscripten一起使用,以及JSPI的路线图是什么。
JSPI is an API that allows WebAssembly applications that use sequential APIs to access Web APIs that are asynchronous. Many Web APIs are crafted in terms of JavaScript Promise
objects: instead of immediately performing the requested operation, they return a Promise
to do so. On the other hand, many applications compiled to WebAssembly come from the C/C++ universe, which is dominated by APIs that block the caller until they are completed.
JSPI是一种API,它允许使用顺序API的WebAssembly应用程序访问异步的Web API。许多Web API都是根据JavaScript Promise
对象精心制作的:它们不是立即执行所请求的操作,而是返回一个Promise
来执行。另一方面,许多编译到WebAssembly的应用程序来自C/C++世界,该世界由API主导,这些API会阻止调用者直到完成。
JSPI hooks into the Web architecture to allow a WebAssembly application to be suspended when the Promise
is returned and resumed when the Promise
is resolved.
JSPI挂钩到Web架构中,允许WebAssembly应用程序在返回Promise
时挂起,并在解析Promise
时恢复。
You can find out more about JSPI and how to use it in this blog post and in the specification.
您可以在这篇博客文章和规范中找到更多关于JSPI以及如何使用它的信息。
What is new?有什么新的?
The end of Suspender
objects
In January 2024, the Stacks sub-group of the Wasm CG voted to amend the API for JSPI. Specifically, instead of an explicit Suspender
object, we will use the JavaScript/WebAssembly boundary as the delimiter for determining what computations are suspended.
2024年1月,Wasm CG的Stacks小组投票决定修改JSPI的API。具体地说,我们将使用JavaScript/WebAssembly边界来确定哪些计算被挂起,而不是显式的Suspender
对象。
The difference is fairly small but potentially significant: when a computation is to be suspended, it is the most recent call into a wrapped WebAssembly export that determines the 'cut point' for what is suspended.
差异相当小,但可能很重要:当计算被挂起时,它是对包装的WebAssembly导出的最新调用,它确定了挂起的“切割点”。
The implication of this is that a developer using JSPI has a little less control over that cut point. On the other hand, not having to explicitly manage Suspender
objects makes the API significantly easier to use.
这意味着使用JSPI的开发人员对该分界点的控制要少一些。另一方面,不需要显式管理Suspender
对象使得API更易于使用。
No moreWebAssembly.Function
Another change is to the style of the API. Instead of characterizing JSPI wrappers in terms of the WebAssembly.Function
constructor, we provide specific functions and constructors.
另一个变化是API的样式。我们提供了特定的函数和构造函数,而不是用WebAssembly.Function
构造函数来描述JSPI包装器。
This has a number of benefits:这有许多好处:
-
It removes dependency on the Type Reflection Proposal.
它消除了对类型反射建议的依赖。
-
It makes tooling for JSPI simpler: the new API functions no longer need to refer explicitly to the WebAssembly types of functions.
它使JSPI的工具更简单:新的API函数不再需要显式引用WebAssembly类型的函数。
This change is enabled by the decision to no longer have explicitly referenced Suspender
objects.
这个改变是由不再显式引用Suspender
对象的决定实现的。
Returning without suspending返回而不暂停
A third change refers to the behavior of suspending calls. Instead of always suspending when calling a JavaScript function from a suspending import, we only suspend when the JavaScript function actually returns a Promise
.
第三个变化涉及挂起调用的行为。当从挂起导入调用JavaScript函数时,我们并不总是挂起,而是仅在JavaScript函数实际返回Promise
时挂起。
This change, while apparently going against the recommendations of the W3C TAG, represents a safe optimization for JSPI users. It is safe because JSPI is actually taking on the role of a caller to a function that returns a Promise
.
这种变化,虽然显然违背了W3C TAG的建议,但对JSPI用户来说是一种安全的优化。这是安全的,因为JSPI实际上扮演了一个返回Promise
的函数的调用者的角色。
This change will likely have minimal impact on most applications; however, some applications will see a notable benefit by avoiding unnecessary trips to the browser's event loop.
这一变化可能对大多数应用程序的影响很小;然而,一些应用程序将通过避免不必要的浏览器事件循环而获得显着的好处。
The new API新的API
The API is straightforward: there is a function that takes a function exported from a WebAssembly module and converts it into a function that returns a Promise
:
API很简单:有一个函数,它接受从WebAssembly模块导出的函数,并将其转换为返回Promise
的函数:
Function Webassembly.promising(Function wsFun)
Note that even if the argument is typed as a JavaScript Function
, it is actually restricted to WebAssembly functions.
请注意,即使参数类型为JavaScript Function
,它实际上也仅限于WebAssembly函数。
On the suspending side, there's a new class WebAssembly.Suspending
, together with a constructor that takes a JavaScript function as an argument. In WebIDL, this is written as follows:
在挂起方面,有一个新的类WebAssembly.Suspending
,以及一个接受JavaScript函数作为参数的构造函数。在WebIDL中,它是这样写的:
interface Suspending{
constructor (Function fun);
}
Note that this API has an asymmetric feel to it: there's have a function that takes a WebAssembly function and returns a new promising (sic) function; whereas to mark a suspending function, you enclose it in a Suspending
object. This reflects a deeper reality about what is happening under the hood.
请注意,这个API有一种不对称的感觉:有一个函数接受WebAssembly函数并返回一个新的有希望的函数;而要标记一个挂起的函数,您将其封装在Suspending
对象中。这反映了关于引擎盖下正在发生的事情的更深层次的现实。
The suspending behavior of an import is intrinsically part of the call to the import: i.e., some function inside the instantiated module calls the import and suspends as a result.
导入的挂起行为本质上是对导入的调用的一部分:即,实例化模块中的某个函数调用导入并因此挂起。
On the other hand, the promising
function takes a regular WebAssembly function and returns a new one that can respond to being suspended and which returns a Promise
.
另一方面,promising
函数接受一个常规的WebAssembly函数,并返回一个新的函数,该函数可以响应被挂起,并返回Promise
。
Using the new API使用新的API
If you are an Emscripten user, then using the new API will typically involve no changes to your code. You must be using a version of Emscripten that is at least 3.1.61, and you must be using a version of Chrome that is at least 126.0.6478.17 (Chrome M126).
如果您是Emscripten用户,那么使用新的API通常不会涉及对代码的更改。您必须使用的Emscripten版本至少为3.1.61,并且您必须使用的Chrome版本至少为126.0.6478.17(Chrome M126)。
If you are rolling your own integration, then your code should be significantly simpler. In particular, it is no longer necessary to have code that stores the passed-in Suspender
object (and retrieve it when calling the import). You can simply use regular sequential code within the WebAssembly module.
如果您正在滚动自己的集成,那么您的代码应该明显更简单。特别是,不再需要存储传入的Suspender
对象的代码(并在调用导入时检索它)。您可以简单地在WebAssembly模块中使用常规顺序代码。
The old API旧的API
The old API will continue to operate at least until October 29, 2024 (Chrome M128). After that, we plan on removing the old API.
旧的API将继续运行至少到2024年10月29日(Chrome M128)。之后,我们计划删除旧的API。
Note that Emscripten itself will no longer support the old API as of version 3.1.61.
请注意,Emscripten本身将不再支持3.1.61版的旧API。
Detecting which API is in your browser检测浏览器中的API
Changing APIs should never be taken lightly. We are able to do so in this case because JSPI itself is still provisional. There is a simple way that you can test to see which API is enabled in your browser:
改变API永远不应该掉以轻心。在这种情况下,我们能够这样做,因为JSPI本身仍然是临时的。有一种简单的方法可以测试浏览器中启用了哪些API:
function oldAPI(){
return WebAssembly.Suspender!=undefined
}
function newAPI(){
return WebAssembly.Suspending!=undefined
}
What is happening with JSPI?JSPI发生了什么?
Implementation aspects实施工作层面
The biggest change to JSPI that we are working on is actually invisible to most programmers: so-called growable stacks.
我们正在做的对JSPI最大的改变实际上对大多数程序员来说是不可见的:所谓的可增长堆栈。
The current implementation of JSPI is based on allocating stacks of a fixed size. In fact, the allocated stacks are rather large. This is because we have to be able to accommodate arbitrary WebAssembly computations which may require deep stacks to handle recursion properly.
JSPI的当前实现是基于分配固定大小的堆栈。实际上,分配的堆栈相当大。这是因为我们必须能够适应任意的WebAssembly计算,这些计算可能需要深度堆栈才能正确处理递归。
However, this is not a sustainable strategy: we would like to support applications with millions of suspended coroutines; this is not possible if each stack is 1MB in size.
然而,这不是一个可持续的策略:我们希望支持具有数百万个挂起的协程的应用程序;如果每个堆栈的大小为1 MB,这是不可能的。
Growable stacks refers to a stack allocation strategy that allows a WebAssembly stack to grow as needed. That way, we can start with very small stacks for those applications that only need small stack space, and grow the stack when the application runs out of space (otherwise known as stack overflow).
Growable stacks是指允许WebAssembly堆栈根据需要增长的堆栈分配策略。这样,对于那些只需要很小堆栈空间的应用程序,我们可以从很小的堆栈开始,当应用程序耗尽空间时(也称为堆栈溢出),可以增加堆栈。
There are several potential techniques for implementing growable stacks. One that we are investigating is segmented stacks. A segmented stack consists of a chain of stack regions — each of which has a fixed size, but different segments may have different sizes.
有几种潜在的技术可以实现可增长的堆栈。我们正在研究的一个是分段堆栈。分段堆栈由一系列堆栈区域组成,每个区域都有固定的大小,但不同的段可能有不同的大小。
Note that while we may be solving the stack overflow issue for coroutines, we are not planning to make the main or central stack growable. Thus, if your application runs out of stack space, growable stacks will not fix your problem unless you use JSPI.
请注意,虽然我们可能会解决协程的堆栈溢出问题,但我们并不打算使主堆栈或中心堆栈可增长。因此,如果应用程序耗尽了堆栈空间,除非使用JSPI,否则可增长的堆栈将无法解决问题。
The standards process标准进程
As of publication, there is an active origin trial for JSPI. The new API will be live during the remainder of the origin trial — available with Chrome M126.
截至出版时,JSPI正在进行原产地试验。新的API将在Origin试用期的剩余时间内使用-可在Chrome M126中使用。
The previous API will also be available during the origin trial; however, it is planned to be retired shortly after Chrome M128.
之前的API也将在Origin试用期间提供;但计划在Chrome M128之后不久退役。
After that, the main thrust for JSPI revolves around the standardization process. JSPI is currently (at publication time) in phase 3 of the W3C Wasm CG process. The next step, i.e., moving to phase 4, marks the crucial adoption of JSPI as a standard API for the JavaScript and WebAssembly ecosystems.
在此之后,JSPI的主要推动力围绕着标准化过程。JSPI目前(在发布时)处于W3C Wasm CG进程的第3阶段。下一步,即,进入第4阶段,标志着JSPI作为JavaScript和WebAssembly生态系统的标准API的重要采用。