Difference between revisions of "WebAssembly/JS-Promise Integration"

From Free Pascal wiki
Jump to navigationJump to search
(→‎See Also: - added link to the official proposal overview)
(→‎Suspending externals: - more details on implementing suspending functions on the JavaScript side)
Line 14: Line 14:
 
Suspending externals allows calling JavaScript functions that are run asynchronously on the JavaScript side, even though they are called as if they are synchronous on the WebAssembly side. Example:
 
Suspending externals allows calling JavaScript functions that are run asynchronously on the JavaScript side, even though they are called as if they are synchronous on the WebAssembly side. Example:
 
  function compute_delta: double; external 'js' suspending;
 
  function compute_delta: double; external 'js' suspending;
TODO: add info how to implement compute_delta on the JavaScript side.
+
Or:
 +
function compute_delta: double; external 'js' suspending first;
 +
 
 +
Now we can implement this function asynchronously on the JavaScript side:
 +
var compute_delta = () =>
 +
  fetch('https://example.com/data.txt')
 +
    .then(res => res.text())
 +
    .then(txt => parseFloat(txt));
 +
 
 +
We then prepare the asynchronous function for use as a suspending WebAssembly external, like this:
 +
var suspending_compute_delta = new WebAssembly.Function(
 +
  {parameters:[externref],results:['f64']},
 +
  compute_delta,
 +
  {suspending:"first"}
 +
)
 +
 
 +
We can the use this to form the complete import object:
 +
var importObj = {js: {
 +
    compute_delta:suspending_compute_delta}};
 +
 
 +
Note the extra ''externref'' parameter, as well as the extra attribute ''{suspending:"first"}''. The extra ''externref'' parameter is automatically added, when a function is declared 'suspending'. It contains a special suspender object that is used internally by the WebAssembly JavaScript Promise Integration extension. For more details, see the [https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md#specification spec]. Free Pascal automatically adds the suspender as an extra first parameter for functions that are declared external suspending. There's also an option to add it as a last parameter. In this case, the declaration in Free Pascal needs to be changed to:
 +
function compute_delta: double; external 'js' suspending last;
 +
And on the JavaScript side, the value of the ''suspending'' attribute needs to be changed to: ''{suspending:"last"}''.
 +
 
 
== Promising exports ==
 
== Promising exports ==
 
In order for calls to suspending functions to work, WebAssembly code execution must be entered via an export, marked as 'promising'. Example:
 
In order for calls to suspending functions to work, WebAssembly code execution must be entered via an export, marked as 'promising'. Example:

Revision as of 16:54, 20 June 2023

The WebAssembly JavaScript-Promise Integration Proposal is a proposed extension to the browser JavaScript API: [1]

Browser support

There is currently experimental support implemented in Chrome, Chromium and Edge. So far, browser support is only implemented on the x86_64 and aarch64 platforms. Minimum Chrome versions that support it are 110.0.5473.0 (macOS) / 110.0.5469.0 (Windows, Android) / 110.0.5478.4 (Linux). As an experimental feature, it is disabled by default. To enable it, go to the URL:

chrome://flags

Search for "Experimental WebAssembly JavaScript Promise Integration (JSPI)". Click on the box, select "enable", then restart the browser to enable the feature.

Free Pascal support

Work on adding Free Pascal support for this extension is being done in the wasm_js_promise_integration branch: [2]

Free Pascal language extensions

Suspending externals

Suspending externals allows calling JavaScript functions that are run asynchronously on the JavaScript side, even though they are called as if they are synchronous on the WebAssembly side. Example:

function compute_delta: double; external 'js' suspending;

Or:

function compute_delta: double; external 'js' suspending first;

Now we can implement this function asynchronously on the JavaScript side:

var compute_delta = () => 
  fetch('https://example.com/data.txt')
    .then(res => res.text())
    .then(txt => parseFloat(txt));

We then prepare the asynchronous function for use as a suspending WebAssembly external, like this:

var suspending_compute_delta = new WebAssembly.Function(
  {parameters:[externref],results:['f64']},
  compute_delta,
  {suspending:"first"}
)

We can the use this to form the complete import object:

var importObj = {js: {
    compute_delta:suspending_compute_delta}};

Note the extra externref parameter, as well as the extra attribute {suspending:"first"}. The extra externref parameter is automatically added, when a function is declared 'suspending'. It contains a special suspender object that is used internally by the WebAssembly JavaScript Promise Integration extension. For more details, see the spec. Free Pascal automatically adds the suspender as an extra first parameter for functions that are declared external suspending. There's also an option to add it as a last parameter. In this case, the declaration in Free Pascal needs to be changed to:

function compute_delta: double; external 'js' suspending last;

And on the JavaScript side, the value of the suspending attribute needs to be changed to: {suspending:"last"}.

Promising exports

In order for calls to suspending functions to work, WebAssembly code execution must be entered via an export, marked as 'promising'. Example:

var
  state: double;

function update_state: double;
begin
  state := state + compute_delta;  // this calls the suspending function 'compute_delta'
  update_state := state;
end;

exports
  update_state promising;

TODO: add info how to invoke a promising function from the JavaScript side.

See Also