You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: proposals/js-promise-integration/Overview.md
+26-15
Original file line number
Diff line number
Diff line change
@@ -23,8 +23,8 @@ partial namespace WebAssembly {
23
23
enum Position { "first", "last", "none"}; /* A special argument may be the last or the first one */
24
24
25
25
dictionary Usage {
26
-
Position suspending = "none"; /* By default, functions dont suspend */
27
-
Position promising = "none"; /* By default, functions dont reify Promises */
26
+
Position suspending = "none"; // By default, functions dont suspend
27
+
Position promising = "none"; // By default, functions dont reify Promises
28
28
}
29
29
30
30
[Constructor(FunctionType type, function func, Usage usage)]
@@ -45,7 +45,7 @@ If the `suspending` attribute is set on an imported function, then the `suspendi
45
45
46
46
This way a WebAssembly module can import a `suspending` function that wraps an async JavaScript function so that the WebAssembly module's computation suspends until the `Promise` is resolved, letting the WebAssembly code nearly treat the call as a synchronous call.
47
47
48
-
But, for that to work, the WebAssembly module needs a way to get a `Suspender` to supply as the first/last argument to the imported function. This is addressed by marking an exported function with the `promising` attribute. The `promising` attribute is used to indicate that the wrapped function should be executed on a new suspendible stack, i.e. a `Suspender`. In addition, the `promising` function will return a `Promise` if WebAssembly computation is suspended.
48
+
But, for that to work, the WebAssembly module needs a way to get a `Suspender` to supply as the first/last argument to the imported function. This is addressed by marking an exported function with the `promising` attribute. The `promising` attribute is used to indicate that the wrapped function should be executed on a new suspendible stack, i.e. a `Suspender`. In addition, the `promising` function will return a `Promise`which will be resolved either with the successful results of the export or rejected if the export throws an exception.
49
49
50
50
The `Suspender` that the wrapped function is executed on is supplied by the engine as its first/last argument.
51
51
This way it can be passed to `suspending` functions down the call stack so that they can suspend all WebAssembly computation up to the respective `promising` function call. At that point, the `promising` function returns a `Promise` that will resolve once the `Promise` that prompted the suspension resolves and the subsequently resumed WebAssembly computation completes.
@@ -58,7 +58,7 @@ It is the responsibility of the WebAssembly program to ensure that this `Suspend
58
58
59
59
`Suspender` objects are _not_ directly visible to either the JavaScript programmer or the WebAssembly programmer. The latter sees them as opaque `externref` values and the former only sees them if they were exported by the WebAssembly module as an exported global variable or passed as an argument to an unmarked import.
60
60
61
-
In particular, a `suspending` function does not actually pass its first/last argument to its wrapped function; that argument is only used to suspend the containing computation should the wrapped function return a `Promise`.
61
+
In particular, a `suspending` function does not actually pass its first/last argument to its imported function; that argument is only used to suspend the containing computation should the wrapped function return a `Promise`.
62
62
63
63
## Examples
64
64
Considering the expected applications of this API, we can consider two simple scenarios: that of a so-called _legacy C_ application—which is written in the style of a non-interactive application using synchronous APIs for reading and writing files—and the _responsive C_ application; where the application was typically written using an eventloop internal architecture but still uses synchronous APIs for I/O.
@@ -103,7 +103,8 @@ The import helper implements the function we want—`compute_delta`—in
We prepare the JavaScript `compute_delta` function for our use by constructing a `WebAssembly.Function` object from it, and setting the `suspending` attribute to `first`:
109
110
```
@@ -115,7 +116,7 @@ var suspending_compute_delta = new WebAssembly.Function(
115
116
```
116
117
There are three possiblities for assigning a value to the `suspending` attribute: `"first"`, `"last"` and `"none"`. These relate to which argument of `$compute_delta_import` actually has the suspender. In our case it makes no difference whether we use `"first"` or `"last"` because the are no other arguments. Using `"none"` is a signal that the function is not actually suspending.
117
118
118
-
The return type of `suspending_compute_delta` is an `"f64"`, because that is what the WebAssembly module is importing. However, the actual function that is executed returns a `Promise` of a `f64`. The importing WebAssembly module never sees that `Promise` object—it is consumed by the function generated via teh`WebAssembly.Function` constructor.
119
+
The return type of `suspending_compute_delta` is an `"f64"`, because that is what the WebAssembly module is importing. However, the actual function that is executed returns a `Promise` of a `f64`. The importing WebAssembly module never sees that `Promise` object—it is consumed by the function generated via the`WebAssembly.Function` constructor.
119
120
120
121
The complete import object looks like:
121
122
```
@@ -184,6 +185,10 @@ The required change is located in the call to `$compute_delta_import`:
184
185
)
185
186
```
186
187
188
+
Not all applications can equally tolerate being reentrant in this way. Certainly, languages in the C family do not make this straightforward. In fact, an application would typically have to have been engineered appropriately, by, for example, ensuring that important global state is properly managed.
189
+
190
+
However, desktop applications, written for operating systems such as Mac OS and Windows, are often already structured in terms of an event loop that monitors input events and schedules UI effects. Such an application can often make good use of JSPI: perhaps by removing the application's event loop and replacing it with the browser's event loop.
191
+
187
192
## Specification
188
193
189
194
The `Suspender` object specified here is not made available to JavaScript via this API. Unless exported via some form of back-channel it will not participate in normal JavaScript execution. However, it does have an internal role within the API and so it is specified.
@@ -228,8 +233,7 @@ The WebAssembly function returned by `WebAssembly.Function` is a function whose
228
233
229
234
A function is suspendable if it was
230
235
* defined by a WebAssembly module,
231
-
* returned by `suspendOnReturnedPromise`,
232
-
* returned by `returnPromiseOnSuspend`,
236
+
* returned by `WebAssembly.function`,
233
237
* or generated by [creating a host function](https://webassembly.github.io/spec/js-api/index.html#create-a-host-function) for a suspendable function
234
238
235
239
Importantly, functions written in JavaScript are *not* suspendable, conforming to feedback from members of [TC39](https://tc39.es/), and host functions (except for the few listed above) are *not* suspendable, conforming to feedback from engine maintainers.
@@ -252,16 +256,23 @@ if the value of `promising` is `"last"`.
252
256
253
257
If the value of `promising` is `"none"`, then this specification does not apply to the constructed function.
254
258
255
-
Note that the return type of the `WebAssembly.Function` is fixed to `externref`. This is because the constructed function may return a `Promise`. The constructed function is not always expected to return a `Promise`—if the `func`returns normally as opposed to suspending then it will typically not return a `Promise`. Type consistentency requires that any non-`Promise` return value must be boxed as an `externref`. This boxing is implemented as part of the constructed function.
259
+
Note that the return type of the `WebAssembly.Function` is fixed to `externref`. This is because the constructed function returns a `Promise`.
256
260
257
261
0. Let `func` be the function that is passed to the `WebAssembly.Function` constructor,
258
262
1. the function that is created using this variant of the `WebAssembly.Function` constructor will, when called with arguments `args`:
259
-
1. Allocate a new `Suspender` object and pass it as an additional argument to `args` to the `func` argument in the `WebAssembly.Function` constructor.
260
-
2. Changes the state of `suspender`'s state to **Active**[`caller`] (where `caller` is the current caller)
261
-
3. Lets `result` be the result of calling `func(args)` (or any trap or thrown exception)
262
-
4. Asserts that `suspender`'s state is **Active**[`caller'`] for some `caller'` (should be guaranteed, though the caller might have changed)
263
-
5. Changes `suspender`'s state to **Moribund**. This is also an opportunity to release any execution resources associated with the suspender. A **Moribund** suspender may not be used to suspend computations.
264
-
6. Returns (or rethrows) `result` to `caller'`
263
+
1. Let `promise` be a new `Promise` constructed as though by the `Promise`(`fn`) constructor, where `fn` is a function of two arguments `accept` and `reject` that:
264
+
1. lets `suspender` be a new `Suspender` object and passes it as an additional argument to `args` to the `func` argument in the `WebAssembly.Function` constructor:
265
+
1. If `promising` is `"first"`, then `suspender` is the first argument, followed by `args`;
266
+
2. if `promising` is `"last"`, then `suspender` is the last argument, after `args`.
267
+
2. sets the state of `suspender` to **Active**[`caller`] (where `caller` is the current caller)
268
+
3. lets `result` be the result of calling `func(suspender,...args)` (or any trap or thrown exception)
269
+
4. asserts that `suspender`'s state is **Active**[`caller'`] for some `caller'` (should be guaranteed, though the caller might have changed)
270
+
5. changes `suspender`'s state to **Moribund**. This is also an opportunity to release any execution resources associated with the suspender. A **Moribund** suspender may not be used to suspend computations.
271
+
6. If `result` is not an exception or a trap, calls the `accept` function argument with the appropriate value.
272
+
6. If `result` is an exception, or if it is a trap, calls the `reject` function with the raised exception.
273
+
6. Returns `promise` to `caller`
274
+
275
+
Note that, if the inner function `func` suspends (by invoking a `Promise` returning import), then the `promise` will be returned to the `caller` before `func` returns. When `func` completes eventually, then `promise` will be resolved—and one of `accept` or `reject` will be invoked by the browser's microtask runner.
0 commit comments