11.. _asyncify section :
22
3- ========
4- Asyncify
5- ========
3+ =================
4+ Asynchronous Code
5+ =================
66
7- Asyncify lets ** synchronous ** C or C++ code interact with **asynchronous **
8- JavaScript. This allows things like:
7+ Emscripten supports two ways (Asyncify and JSPI) that let **synchronous ** C or
8+ C++ code interact with ** asynchronous ** JavaScript. This allows things like:
99
1010 * A synchronous call in C that yields to the event loop, which
1111 allows browser events to be handled.
1212 * A synchronous call in C that waits for an asynchronous operation in JS to
1313 complete.
1414
15- Asyncify automatically transforms your compiled code into a form that can be
16- paused and resumed, and handles pausing and resuming for you, so that it is
17- asynchronous (hence the name "Asyncify") even though you wrote it in a normal
18- synchronous way.
1915
20- See the
16+ In general the two options are very similar, but rely on different underlying
17+ mechanisms to work.
18+
19+ * `Asyncify ` - Asyncify automatically transforms your compiled code into a
20+ form that can be paused and resumed, and handles pausing and resuming for
21+ you, so that it is asynchronous (hence the name "Asyncify") even though you
22+ wrote it in a normal synchronous way. This works in most environments, but
23+ can cause the Wasm output to be much larger.
24+ * `JSPI ` (experimental) - Uses the VM's support for JavaScript Promise
25+ Integration (JSPI) for interacting with async JavaScript. The code size will
26+ remain the same, but support for this feature is still experimental.
27+
28+ For more on Asyncify see the
2129`Asyncify introduction blogpost <https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html >`_
2230for general background and details of how it works internally (you can also view
2331`this talk about Asyncify <https://www.youtube.com/watch?v=qQOP6jqZqf8 >`_).
@@ -62,11 +70,11 @@ Let's begin with the example from that blogpost:
6270 }
6371 }
6472
65- You can compile that with
73+ You can compile that using either ` -sASYNCIFY ` or ` -sJSPI `
6674
6775::
6876
69- emcc -O3 example.cpp -sASYNCIFY
77+ emcc -O3 example.cpp -s<ASYNCIFY or JSPI>
7078
7179.. note :: It's very important to optimize (``-O3`` here) when using Asyncify, as
7280 unoptimized builds are very large.
@@ -77,6 +85,12 @@ And you can run it with
7785
7886 nodejs a.out.js
7987
88+ Or with JSPI
89+
90+ ::
91+
92+ nodejs --experimental-wasm-stack-switching a.out.js
93+
8094You should then see something like this:
8195
8296::
@@ -90,7 +104,7 @@ You should then see something like this:
90104
91105The code is written with a straightforward loop, which does not exit while
92106it is running, which normally would not allow async events to be handled by the
93- browser. With Asyncify, those sleeps actually yield to the browser's main event
107+ browser. With Asyncify/JSPI , those sleeps actually yield to the browser's main event
94108loop, and the timer can happen!
95109
96110Making async Web APIs behave as if they were synchronous
@@ -132,7 +146,7 @@ To run this example, first compile it with
132146
133147::
134148
135- emcc example.c -O3 -o a.html -sASYNCIFY
149+ emcc example.c -O3 -o a.html -s<ASYNCIFY or JSPI>
136150
137151To run this, you must run a :ref: `local webserver <faq-local-webserver >`
138152and then browse to ``http://localhost:8000/a.html ``.
@@ -148,8 +162,8 @@ You will see something like this:
148162That shows that the C code only continued to execute after the async JS
149163completed.
150164
151- Ways to use async APIs in older engines
152- #######################################
165+ Ways to use Asyncify APIs in older engines
166+ ##########################################
153167
154168If your target JS engine doesn't support the modern ``async/await `` JS
155169syntax, you can desugar the above implementation of ``do_fetch `` to use Promises
@@ -267,13 +281,17 @@ and want to ``await`` a dynamically retrieved ``Promise``, you can call an
267281 val my_object = /* ... */;
268282 val result = my_object.call<val>("someAsyncMethod").await();
269283
270- In this case you don't need to worry about ``ASYNCIFY_IMPORTS ``, since it's an
271- internal implementation detail of ``val::await `` and Emscripten takes care of it
272- automatically.
284+ In this case you don't need to worry about ``ASYNCIFY_IMPORTS `` or
285+ `` JSPI_IMPORTS ``, since it's an internal implementation detail of ``val::await ``
286+ and Emscripten takes care of it automatically.
273287
274- Note that when Asyncify is used with Embind and the code is invoked from
275- JavaScript, then it will be implicitly treated as an ``async `` function,
276- returning a ``Promise `` to the return value, as demonstrated below.
288+ Note that when using Embind exports, Asyncify and JSPI behave differently. When
289+ Asyncify is used with Embind and the code is invoked from JavaScript, then the
290+ function will return a ``Promise `` if the export calls any suspending functions,
291+ otherwise the result will be returned synchronously. However, with JSPI, the
292+ parameter ``emscripten::async() `` must be used to mark the function as
293+ asynchronous and the export will always return a ``Promise `` regardless if the
294+ export suspended.
277295
278296.. code-block :: cpp
279297
@@ -288,15 +306,18 @@ returning a ``Promise`` to the return value, as demonstrated below.
288306 }
289307
290308 EMSCRIPTEN_BINDINGS(example) {
309+ // Asyncify
291310 emscripten::function("delayAndReturn", &delayAndReturn);
311+ // JSPI
312+ emscripten::function("delayAndReturn", &delayAndReturn, emscripten::async());
292313 }
293314
294315 Build with
295316::
296317
297- emcc -O3 example.cpp -lembind -sASYNCIFY
318+ emcc -O3 example.cpp -lembind -s<ASYNCIFY or JSPI>
298319
299- Then invoke from JavaScript
320+ Then invoke from JavaScript (using Asyncify)
300321
301322.. code-block :: javascript
302323
@@ -316,6 +337,19 @@ if Asyncify calls are encountered (such as ``emscripten_sleep()``,
316337If the code path is undetermined, the caller may either check if the returned
317338value is an ``instanceof Promise `` or simply ``await `` on the returned value.
318339
340+ When using JSPI the return values will always be a ``Promise `` as seen below
341+
342+ .. code-block :: javascript
343+
344+ let syncResult = Module .delayAndReturn (false );
345+ console .log (syncResult); // Promise { <pending> }
346+ console .log (await syncResult); // 42
347+
348+ let asyncResult = Module .delayAndReturn (true );
349+ console .log (asyncResult); // Promise { <pending> }
350+ console .log (await asyncResult); // 42
351+
352+
319353 Usage with ``ccall ``
320354####################
321355
@@ -332,8 +366,25 @@ In this example, a function "func" is called which returns a Number.
332366 console .log (" js_func: " + result);
333367 });
334368
335- Optimizing
336- ##########
369+
370+ Differences Between Asyncify and JSPI
371+ #####################################
372+
373+ Besides using different underlying mechanisms, Asyncify and JSPI also handle
374+ async imports and exports differently. Asyncify will automatically determine
375+ what exports will become async based on what could potentially call an
376+ an async import (``ASYNCIFY_IMPORTS ``). However, with JSPI, the async imports
377+ and exports must be explicitly set using ``JSPI_IMPORTS `` and ``JSPI_EXPORTS ``
378+ settings.
379+
380+ .. note :: ``<JSPI/ASYNCIFY>_IMPORTS`` and ``JSPI_EXPORTS`` aren't needed when
381+ using various helpers mentioned above such as: ``EM_ASYNC_JS ``,
382+ Embind's Async support, ``ccall ``, etc...
383+
384+ Optimizing Asyncify
385+ ###################
386+
387+ .. note :: This section does not apply to JSPI.
337388
338389As mentioned earlier, unoptimized builds with Asyncify can be large and slow.
339390Build with optimizations (say, ``-O3 ``) to get good results.
@@ -383,8 +434,8 @@ it's usually ok to use the defaults.
383434Potential problems
384435##################
385436
386- Stack overflows
387- ***************
437+ Stack overflows (Asyncify)
438+ **************************
388439
389440If you see an exception thrown from an ``asyncify_* `` API, then it may be
390441a stack overflow. You can increase the stack size with the
@@ -409,8 +460,8 @@ if a function uses a global and assumes nothing else can modify it until it
409460returns, but if that function sleeps and an event causes other code to
410461change that global, then bad things can happen.
411462
412- Starting to rewind with compiled code on the stack
413- **************************************************
463+ Starting to rewind with compiled code on the stack (Asyncify)
464+ *************************************************************
414465
415466The examples above show `wakeUp() ` being called from JS (after a callback,
416467typically), and without any compiled code on the stack. If there *were * compiled
@@ -426,8 +477,8 @@ A simple workaround you may find useful is to do a setTimeout of 0, replacing
426477``wakeUp() `` with ``setTimeout(wakeUp, 0); ``. That will run ``wakeUp `` in a
427478later callback, when nothing else is on the stack.
428479
429- Migrating from older APIs
430- #########################
480+ Migrating from older Asyncify APIs
481+ ##################################
431482
432483If you have code uses the old Emterpreter-Async API, or the old Asyncify, then
433484almost everything should just work when you replace ``-sEMTERPRETIFY `` usage
0 commit comments