1
1
.. _asyncify section :
2
2
3
- ========
4
- Asyncify
5
- ========
3
+ =================
4
+ Asynchronous Code
5
+ =================
6
6
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:
9
9
10
10
* A synchronous call in C that yields to the event loop, which
11
11
allows browser events to be handled.
12
12
* A synchronous call in C that waits for an asynchronous operation in JS to
13
13
complete.
14
14
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.
19
15
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
21
29
`Asyncify introduction blogpost <https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html >`_
22
30
for general background and details of how it works internally (you can also view
23
31
`this talk about Asyncify <https://www.youtube.com/watch?v=qQOP6jqZqf8 >`_).
@@ -62,11 +70,11 @@ Let's begin with the example from that blogpost:
62
70
}
63
71
}
64
72
65
- You can compile that with
73
+ You can compile that using either ` -sASYNCIFY ` or ` -sJSPI `
66
74
67
75
::
68
76
69
- emcc -O3 example.cpp -sASYNCIFY
77
+ emcc -O3 example.cpp -s<ASYNCIFY or JSPI>
70
78
71
79
.. note :: It's very important to optimize (``-O3`` here) when using Asyncify, as
72
80
unoptimized builds are very large.
@@ -77,6 +85,12 @@ And you can run it with
77
85
78
86
nodejs a.out.js
79
87
88
+ Or with JSPI
89
+
90
+ ::
91
+
92
+ nodejs --experimental-wasm-stack-switching a.out.js
93
+
80
94
You should then see something like this:
81
95
82
96
::
@@ -90,7 +104,7 @@ You should then see something like this:
90
104
91
105
The code is written with a straightforward loop, which does not exit while
92
106
it 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
94
108
loop, and the timer can happen!
95
109
96
110
Making async Web APIs behave as if they were synchronous
@@ -132,7 +146,7 @@ To run this example, first compile it with
132
146
133
147
::
134
148
135
- emcc example.c -O3 -o a.html -sASYNCIFY
149
+ emcc example.c -O3 -o a.html -s<ASYNCIFY or JSPI>
136
150
137
151
To run this, you must run a :ref: `local webserver <faq-local-webserver >`
138
152
and then browse to ``http://localhost:8000/a.html ``.
@@ -148,8 +162,8 @@ You will see something like this:
148
162
That shows that the C code only continued to execute after the async JS
149
163
completed.
150
164
151
- Ways to use async APIs in older engines
152
- #######################################
165
+ Ways to use Asyncify APIs in older engines
166
+ ##########################################
153
167
154
168
If your target JS engine doesn't support the modern ``async/await `` JS
155
169
syntax, 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
267
281
val my_object = /* ... */;
268
282
val result = my_object.call<val>("someAsyncMethod").await();
269
283
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.
273
287
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.
277
295
278
296
.. code-block :: cpp
279
297
@@ -288,15 +306,18 @@ returning a ``Promise`` to the return value, as demonstrated below.
288
306
}
289
307
290
308
EMSCRIPTEN_BINDINGS(example) {
309
+ // Asyncify
291
310
emscripten::function("delayAndReturn", &delayAndReturn);
311
+ // JSPI
312
+ emscripten::function("delayAndReturn", &delayAndReturn, emscripten::async());
292
313
}
293
314
294
315
Build with
295
316
::
296
317
297
- emcc -O3 example.cpp -lembind -sASYNCIFY
318
+ emcc -O3 example.cpp -lembind -s<ASYNCIFY or JSPI>
298
319
299
- Then invoke from JavaScript
320
+ Then invoke from JavaScript (using Asyncify)
300
321
301
322
.. code-block :: javascript
302
323
@@ -316,6 +337,19 @@ if Asyncify calls are encountered (such as ``emscripten_sleep()``,
316
337
If the code path is undetermined, the caller may either check if the returned
317
338
value is an ``instanceof Promise `` or simply ``await `` on the returned value.
318
339
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
+
319
353
Usage with ``ccall ``
320
354
####################
321
355
@@ -332,8 +366,25 @@ In this example, a function "func" is called which returns a Number.
332
366
console .log (" js_func: " + result);
333
367
});
334
368
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.
337
388
338
389
As mentioned earlier, unoptimized builds with Asyncify can be large and slow.
339
390
Build with optimizations (say, ``-O3 ``) to get good results.
@@ -383,8 +434,8 @@ it's usually ok to use the defaults.
383
434
Potential problems
384
435
##################
385
436
386
- Stack overflows
387
- ***************
437
+ Stack overflows (Asyncify)
438
+ **************************
388
439
389
440
If you see an exception thrown from an ``asyncify_* `` API, then it may be
390
441
a 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
409
460
returns, but if that function sleeps and an event causes other code to
410
461
change that global, then bad things can happen.
411
462
412
- Starting to rewind with compiled code on the stack
413
- **************************************************
463
+ Starting to rewind with compiled code on the stack (Asyncify)
464
+ *************************************************************
414
465
415
466
The examples above show `wakeUp() ` being called from JS (after a callback,
416
467
typically), 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
426
477
``wakeUp() `` with ``setTimeout(wakeUp, 0); ``. That will run ``wakeUp `` in a
427
478
later callback, when nothing else is on the stack.
428
479
429
- Migrating from older APIs
430
- #########################
480
+ Migrating from older Asyncify APIs
481
+ ##################################
431
482
432
483
If you have code uses the old Emterpreter-Async API, or the old Asyncify, then
433
484
almost everything should just work when you replace ``-sEMTERPRETIFY `` usage
0 commit comments