|
| 1 | +# Running `async` functions in WebAssembly |
| 2 | + |
| 3 | +On macOS, iOS, and Linux, `libdispatch`-based executor is used by default, but `libdispatch` is not supported in single-threaded WebAssembly environment. |
| 4 | +However, there are still two global task executors available in SwiftWasm. |
| 5 | + |
| 6 | +## Cooperative Task Executor |
| 7 | + |
| 8 | +`Cooperative Task Executor` is the default task executor in SwiftWasm. It is a simple single-threaded cooperative task executor implemented in [Swift Concurrency library](https://github.com/apple/swift/blob/0c67ce64874d83b2d4f8d73b899ee58f2a75527f/stdlib/public/Concurrency/CooperativeGlobalExecutor.inc). |
| 9 | +If you are not familiar with "Cooperative" in concurrent programming term, see [its definition for more details](https://en.wikipedia.org/wiki/Cooperative_multitasking). |
| 10 | + |
| 11 | +This executor has an *event loop* that dispatches tasks until no more tasks are enqueued, and exits immediately after all tasks are dispatched. |
| 12 | +Note that this executor won't yield control to the host environment during execution, so any host's async operation cannot call back to the Wasm execution. |
| 13 | + |
| 14 | +This executor is suitable for WASI command line tools, or host-independent standalone applications. |
| 15 | + |
| 16 | +```swift |
| 17 | +// USAGE |
| 18 | +// $ swiftc -target wasm32-unknown-wasi -parse-as-library main.swift -o main.wasm |
| 19 | +// $ wasmtime main.wasm |
| 20 | +@main |
| 21 | +struct Main { |
| 22 | + static func main() async throws { |
| 23 | + print("Sleeping for 1 second... 😴") |
| 24 | + try await Task.sleep(nanoseconds: 1_000_000_000) |
| 25 | + print("Wake up! 😁") |
| 26 | + } |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +## JavaScript Event Loop-based Task Executor |
| 31 | + |
| 32 | +`JavaScript Event Loop-based Task Executor` is a task executor that cooperates with the JavaScript's event loop. It is provided by [`JavaScriptKit`](https://github.com/swiftwasm/JavaScriptKit), and you need to activate it explicitly by calling a predefined `JavaScriptEventLoop.installGlobalExecutor()` function (see below for more details). |
| 33 | + |
| 34 | +This executor also has its own *event loop* that dispatches tasks until no more tasks are enqueued synchronously. |
| 35 | +It yields control to the JavaScript side after all pending tasks are dispatched, so JavaScript program can call back to the executed Wasm module. |
| 36 | +After a task is resumed by callbacks from JavaScript, the executor starts its event loop again in the next microtask tick. |
| 37 | + |
| 38 | +To enable this executor, you need to use `JavaScriptEventLoop` module, which is provided as a part of `JavaScriptKit` package. |
| 39 | + |
| 40 | +0. Ensure that you added `JavaScriptKit` dependency to your `Package.swift` |
| 41 | +1. Add `JavaScriptEventLoop` dependency to your targets that use this executor |
| 42 | + |
| 43 | +```swift |
| 44 | +.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), |
| 45 | +``` |
| 46 | +2. Import `JavaScriptEventLoop` and call `JavaScriptEventLoop.installGlobalExecutor()` before spawning any tasks to activate the executor instead of the default cooperative executor. |
| 47 | + |
| 48 | +Note that this executor is only available on JavaScript host environment. |
| 49 | + |
| 50 | +See also [`JavaScriptKit` package `README`](https://github.com/swiftwasm/JavaScriptKit/#asyncawait) for more details. |
| 51 | + |
| 52 | +```swift |
| 53 | +import JavaScriptEventLoop |
| 54 | +import JavaScriptKit |
| 55 | + |
| 56 | +JavaScriptEventLoop.installGlobalExecutor() |
| 57 | + |
| 58 | +let document = JSObject.global.document |
| 59 | +var asyncButtonElement = document.createElement("button") |
| 60 | +_ = document.body.appendChild(asyncButtonElement) |
| 61 | + |
| 62 | +asyncButtonElement.innerText = "Fetch Zen" |
| 63 | +func printZen() async throws { |
| 64 | + let fetch = JSObject.global.fetch.function! |
| 65 | + let response = try await JSPromise(fetch("https://api.github.com/zen").object!)!.value |
| 66 | + let text = try await JSPromise(response.text().object!)!.value |
| 67 | + print(text) |
| 68 | +} |
| 69 | +asyncButtonElement.onclick = .object(JSClosure { _ in |
| 70 | + Task { |
| 71 | + do { |
| 72 | + try await printZen() |
| 73 | + } catch { |
| 74 | + print(error) |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + return .undefined |
| 79 | +}) |
| 80 | +``` |
0 commit comments