Tested with Rinf
tokio_with_wasm
is a Rust library that provides tokio
specifically designed for web browsers. It aims to provide the exact same tokio
features for web applications, leveraging JavaScript web API.
This library is made up of JavaScript glue code that mimics the behavior of real tokio
. Because tokio_with_wasm
doesn't have its own runtime and adapts to the JavaScript event loop, advanced features of tokio
might not work.
When using spawn_blocking()
, the number of web workers are automatically adjusted adapting to the number of parallel tasks. Refer to the docs for additional details.
This library assumes that you're compilng your Rust project with wasm-pack
and wasm-bindgen
, which currently uses wasm32-unknown-unknown
Rust target. Note that this library currently only supports the web
target of wasm-bindgen
, not others such as no-modules
.
-
Familiar API: If you're familiar with
tokio
, you'll feel right at home withtokio_with_wasm
. It provides similar functionality and follows the same patterns for spawning and managing asynchronous tasks. -
Web Worker Integration:
tokio_with_wasm
adapts to the JavaScript environment by utilizing web API under the hood. This means you can write Rust code that runs concurrently and efficiently in web applications. -
Spawn Async and Blocking Tasks: You can spawn both asynchronous and blocking tasks. Asynchronous tasks allow you to perform non-blocking operations, while blocking tasks are suitable for compute-heavy or synchronous tasks.
Though various IO functionalities can be added in the future, they're not included yet.
Add this library to your Cargo.toml
alongside tokio
:
[dependencies]
tokio = { version = "0.0.0", features = ["rt"] }
tokio_with_wasm = { version = "0.0.0", features = ["rt"] }
Here's a simple example of how to use tokio_with_wasm
:
use tokio_with_wasm::alias as tokio;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let async_join_handle = tokio::spawn(async {
// Your asynchronous code here.
// This will run concurrently
// in the same web worker(thread).
});
let blocking_join_handle = tokio::task::spawn_blocking(|| {
// Your blocking code here.
// This will run parallelly
// in the external pool of web workers.
});
let async_result = async_join_handle.await;
let blocking_result = blocking_join_handle.await;
for i in 1..1000 {
// Some repeating task here
// that shouldn't block the JavaScript runtime.
tokio::task::yield_now().await;
}
}
The use tokio_with_wasm::alias as tokio;
statement is functionally equivalent to the code below. This import is provided for convenience and to allow for shorter code.
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
use tokio_with_wasm as tokio;
#[cfg(not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
)))]
use tokio;
API documentation can be found on docs.rs.
Keep in mind that you should NEVER write panicking code.
On wasm32-unknown-unknown
, there's currently no way to catch and unwind panics like on native platforms. Panics will eventually lead to leaked JavaScript Promise
s.
Stick to the Result
enum whenever possible.
If you're using Web Workers (threads) by calling spawn_blocking
, you need to set specific Rust compiler flags:
+atomics
+bulk-memory
+mutable-globals
After building your webassembly module and preparing it for deployment, ensure that your web server is configured to include cross-origin-related HTTP headers in its responses. Set the Cross-Origin-Opener-Policy
to same-origin
and Cross-Origin-Embedder-Policy
to require-corp
. These headers enable clients using your website to gain access to SharedArrayBuffer
web API, which is something similar to shared memory on the web. Additionally, don't forget to specify the MIME type application/wasm
for .wasm
files within the server configurations to ensure optimal performance.
The web has many restrictions due to its sandboxed environment which prevents the use of threads, time, file IO, network IO, and many other native functionalities. Consequently, certain features are missing from Rust's std
due to these limitations. That's why tokio
doesn't really work well on web browsers.
To address this issue, this crate offers tokio
modules with the same names as the original native ones, providing workarounds for these constraints.
Because a large portion of Rust's web ecosystem is based on wasm32-unknown-unknown
right now, we had to make an alias crate of tokio
to use its functionalities directly on the web.
Hopefully, when wasm32-wasi
becomes the mainstream Rust target for the web, jco
might be an alternative to wasm-bindgen
as it can provide full std
functionalities with browser shims (polyfills). However, this will take time because the wasi-threads
proposal still has a long way to go.
Until that time, there's tokio_with_wasm
!
Contributions are always welcome! If you have any suggestions, bug reports, or want to contribute to the development of tokio_with_wasm
, please open an issue or submit a pull request.
There are situations where you cannot use native Rust code directly on the web. This is because wasm32-unknown-unknown
Rust target used by wasm-bindgen
doesn't have a full std
module. Refer to the links below to understand how to interact with JavaScript with wasm-bindgen
.
- https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/js_name.html
- https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/js_namespace.html
It is possible for rust code to be called in a web worker. Therefore, we cannot access the global window
JavaScript object
just like when you work in the main thread of JavaScript. Refer to the link below to check which web APIs are available in a web worker.
You'll be surprised by various capabilities that modern JavaScript has.
Please note that this library uses a quite hacky and naive approach to mimic native tokio
functionalities. That's because this library is regarded as a temporary solution for the period before wasm32-wasi
. Any kind of PR is possible, as long as it makes things just work on the web.