WebAssembly/JS-Promise Integration: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | The WebAssembly JavaScript-Promise Integration Proposal is a proposed extension to the browser JavaScript API: [1](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md) It allows calling asynchronous JavaScript functions from WebAssembly code as if they are synchronous, as well as calling WebAssembly functions asynchronously from JavaScript, even though they are implemented as synchronous. | |
| 2 | 2 | ||
| 3 | 3 | # Browser support | |
| 4 | 4 | ||
| 5 | 5 | 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: | |
| 6 | 6 | ||
| 7 | - | ||
| 7 | + | ```text | |
| 8 | + | chrome://flags | |
| 9 | + | ``` | |
| 8 | 10 | ||
| 9 | 11 | Search for "Experimental WebAssembly JavaScript Promise Integration (JSPI)". Click on the box, select "enable", then restart the browser to enable the feature. | |
| 10 | 12 | ||
| 11 | 13 | # Free Pascal support | |
| 12 | 14 | ||
| 13 | 15 | Free Pascal support for this extension was merged into the main FPC branch on Jun 26 2023. | |
| 14 | 16 | ||
| 15 | 17 | # Free Pascal language extensions | |
| 16 | 18 | ||
| 17 | 19 | ## Suspending externals | |
| 18 | 20 | ||
| 19 | 21 | 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: | |
| 20 | 22 | ||
| 21 | - | ||
| 23 | + | ``` pascal | |
| 24 | + | function compute_delta: double; external 'js' suspending; | |
| 25 | + | ``` | |
| 22 | 26 | ||
| 23 | 27 | Or: | |
| 24 | 28 | ||
| 25 | - | ||
| 29 | + | ``` pascal | |
| 30 | + | function compute_delta: double; external 'js' suspending first; | |
| 31 | + | ``` | |
| 26 | 32 | ||
| 27 | 33 | Now we can implement this function asynchronously on the JavaScript side: | |
| 28 | 34 | ||
| 29 | - | ||
| 30 | - | ||
| 31 | - | ||
| 32 | - | ||
| 35 | + | ``` pascal | |
| 36 | + | var compute_delta = () => | |
| 37 | + | fetch('https://example.com/data.txt') | |
| 38 | + | .then(res => res.text()) | |
| 39 | + | .then(txt => parseFloat(txt)); | |
| 40 | + | ``` | |
| 33 | 41 | ||
| 34 | 42 | We then prepare the asynchronous function for use as a suspending WebAssembly external, like this: | |
| 35 | 43 | ||
| 36 | - | ||
| 37 | - | ||
| 38 | - | ||
| 39 | - | ||
| 40 | - | ||
| 44 | + | ``` pascal | |
| 45 | + | var suspending_compute_delta = new WebAssembly.Function( | |
| 46 | + | {parameters:[externref],results:['f64']}, | |
| 47 | + | compute_delta, | |
| 48 | + | {suspending:"first"} | |
| 49 | + | ) | |
| 50 | + | ``` | |
| 41 | 51 | ||
| 42 | 52 | We can the use this to form the complete import object: | |
| 43 | 53 | ||
| 44 | - | ||
| 45 | - | ||
| 54 | + | ``` pascal | |
| 55 | + | var importObj = {js: { | |
| 56 | + | compute_delta:suspending_compute_delta}}; | |
| 57 | + | ``` | |
| 46 | 58 | ||
| 47 | 59 | 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](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md#specification). 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: | |
| 48 | 60 | ||
| 49 | - | ||
| 61 | + | ``` pascal | |
| 62 | + | function compute_delta: double; external 'js' suspending last; | |
| 63 | + | ``` | |
| 50 | 64 | ||
| 51 | 65 | And on the JavaScript side, the value of the *suspending* attribute needs to be changed to: *{suspending:"last"}*. | |
| 52 | 66 | ||
| 53 | 67 | ## Promising exports | |
| 54 | 68 | ||
| 55 | 69 | In order for calls to suspending functions to work, WebAssembly code execution must be entered via an export, marked as 'promising'. Example: | |
| 56 | 70 | ||
| 57 | - | ||
| 58 | - | ||
| 71 | + | ``` pascal | |
| 72 | + | var | |
| 73 | + | state: double; | |
| 59 | 74 | ||
| 60 | - | ||
| 61 | - | ||
| 62 | - | ||
| 63 | - | ||
| 64 | - | ||
| 75 | + | function update_state: double; | |
| 76 | + | begin | |
| 77 | + | state := state + compute_delta; // this calls the suspending function 'compute_delta' | |
| 78 | + | update_state := state; | |
| 79 | + | end; | |
| 65 | 80 | ||
| 66 | - | ||
| 67 | - | ||
| 81 | + | exports | |
| 82 | + | update_state promising; | |
| 83 | + | ``` | |
| 68 | 84 | ||
| 69 | 85 | Once again, a *promising* export has an extra externref parameter automatically added. Here's how it can be wrapper correctly on the JavaScript side: | |
| 70 | 86 | ||
| 71 | - | ||
| 72 | - | ||
| 73 | - | ||
| 74 | - | ||
| 75 | - | ||
| 87 | + | ``` pascal | |
| 88 | + | var sampleModule = WebAssembly.instantiate(demoBuffer,importObj); | |
| 89 | + | var update_state = new WebAssembly.function( | |
| 90 | + | {parameters:[], results:['externref']}, | |
| 91 | + | sampleModule.exports.update_state_export, | |
| 92 | + | {promising : "first"}) | |
| 93 | + | ``` | |
| 76 | 94 | ||
| 77 | 95 | Alternatively, you can use *promising last*. If you export it like this in Pascal: | |
| 78 | 96 | ||
| 79 | - | ||
| 80 | - | ||
| 97 | + | ```text | |
| 98 | + | exports | |
| 99 | + | update_state promising last; | |
| 100 | + | ``` | |
| 81 | 101 | ||
| 82 | 102 | Then you can use *{promising : "last"}* instead of *{promising : "first"}* in JavaScript. | |
| 83 | 103 | ||
| 84 | 104 | # See Also | |
| 85 | 105 | ||
| 86 | 106 | - [A working example](https://www.freepascal.org/~michael/pas2js-demos/jspi/) | |
| 87 | 107 | - [JavaScript-Promise Integration Proposal](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md) | |
| 88 | 108 | - [Introducing the WebAssembly JavaScript Promise Integration API](https://v8.dev/blog/jspi) | |
| 89 | 109 | - [[WebAssembly]] | |
| 90 | 110 | ||
| 91 | 111 | --- | |
| 92 | 112 | *From the Free Pascal wiki, page "WebAssembly/JS-Promise Integration", as it stood on 2023-06-26 after a change by Nickysn, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 93 | 113 | Category: WebAssembly |
