tokio_with_wasm is a Rust library that provides tokio for web browsers. It aims to offer the exact same tokio API to Rust web applications.
This library is made up of JavaScript glue code that mimics the behavior of real tokio. tokio_with_wasm doesn't have its own runtime and adapts to the JavaScript event loop.
When using spawn_blocking(), the number of web workers is automatically adjusted to the number of parallel tasks. Refer to the docs for additional details.
This library assumes that you're compiling your Rust project with wasm-pack and wasm-bindgen, which build for the wasm32-unknown-unknown and wasm64-unknown-unknown Rust targets. Note that this library 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 with tokio_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 APIs 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.
File system: fs reads and writes files in the OPFS, the store every browser keeps on disk for one origin.
net,process, andsignalhave no counterpart on the web, so they are missing from the web build. Using them is a compile error, not a runtime failure.
Add this library to your Cargo.toml alongside tokio:
[dependencies]
tokio = { version = "0.0.0", features = ["macros", "sync", "time", "rt"] }
tokio_with_wasm = { version = "0.0.0", features = ["macros", "sync", "time", "rt"] }
Keep the feature lists of the two dependencies in sync. tokio's features
serve native platforms, and tokio_with_wasm's features enable the web glue.
Here's a simple example of using tokio_with_wasm that works on both native platforms and web browsers:
use tokio::task::{spawn, spawn_blocking, yield_now, JoinSet};
use tokio::time::{interval, sleep};
use tokio_with_wasm::alias as tokio;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let async_join_handle = spawn(async {
// Asynchronous code here.
// This will run concurrently
// in the same web worker (thread).
});
let blocking_join_handle = spawn_blocking(|| {
// Blocking code here.
// This will run in parallel
// 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.
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, allowing for shorter code.
#[cfg(all(
target_family = "wasm",
target_vendor = "unknown",
target_os = "unknown"
))]
use tokio_with_wasm as tokio;
#[cfg(not(all(
target_family = "wasm",
target_vendor = "unknown",
target_os = "unknown"
)))]
use tokio;
API documentation can be found on docs.rs.
Stick to the Result enum whenever possible.
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 Promises.
A panic inside spawn_blocking takes down the web worker that runs it. The
JoinHandle resolves to a JoinError whose is_panic is true, but the panic
payload is lost and the worker's share of the shared memory is never reclaimed.
If you're using Web Workers (threads) by calling spawn_blocking, you need to set specific Rust compiler flags. Also, you must use the nightly toolchain and include certain Rust standard library components in the compilation.
target-feature flags
+atomics+bulk-memory+mutable-globalslink-arg flags
--shared-memory--max-memory=1073741824--import-memory--export=__wasm_init_tls--export=__tls_size--export=__tls_align--export=__tls_basebuild-std components
stdpanic_abortHere's a full example command:
export RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals -C link-arg=--shared-memory -C link-arg=--max-memory=1073741824 -C link-arg=--import-memory -C link-arg=--export=__wasm_init_tls -C link-arg=--export=__tls_size -C link-arg=--export=__tls_align -C link-arg=--export=__tls_base"
export RUSTUP_TOOLCHAIN="nightly"
wasm-pack build <path> --target web -- -Z build-std=std,panic_abort
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. These headers let clients of your website access the SharedArrayBuffer web API, which is the web's counterpart to shared memory.
cross-origin-opener-policy: same-origincross-origin-embedder-policy: require-corp.wasm with the right MIME typeDon't forget to specify the MIME type application/wasm for .wasm files in your HTTP server configuration to ensure optimal performance.
WebAssembly.instantiateStreaming() rejects any other MIME type, so the module falls back to being compiled after the whole download finishes rather than while it arrives.
spawn_blocking runs its web workers from a blob: script by default. Under a
content security policy that forbids blob: workers, such as a browser
extension's script-src 'self', serve
blocking_worker.js
as your own file and point the pool at it:
tokio_with_wasm::only_web::set_worker_script_provider(|| Ok("/blocking_worker.js".into()));
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. That's why tokio doesn't really work well in 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 the 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.
Rust code can be called in a web worker. Therefore, we cannot access the global window JavaScript object
as we can on 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 the various capabilities of modern JavaScript.
Please note that this library uses quite a hacky and naive approach to mimic native tokio functionalities. That's because this library is meant as a temporary solution for the period before wasm32-wasi. Any kind of PR is welcome, as long as it makes things just work on the web.
Rust
98.2%
JavaScript
1.5%
tokio_with_wasm is a Rust library that provides tokio for web browsers. It aims to offer the exact same tokio API to Rust web applications.
This library is made up of JavaScript glue code that mimics the behavior of real tokio. tokio_with_wasm doesn't have its own runtime and adapts to the JavaScript event loop.
When using spawn_blocking(), the number of web workers is automatically adjusted to the number of parallel tasks. Refer to the docs for additional details.
This library assumes that you're compiling your Rust project with wasm-pack and wasm-bindgen, which build for the wasm32-unknown-unknown and wasm64-unknown-unknown Rust targets. Note that this library 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 with tokio_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 APIs 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.
File system: fs reads and writes files in the OPFS, the store every browser keeps on disk for one origin.
net,process, andsignalhave no counterpart on the web, so they are missing from the web build. Using them is a compile error, not a runtime failure.
Add this library to your Cargo.toml alongside tokio:
[dependencies]
tokio = { version = "0.0.0", features = ["macros", "sync", "time", "rt"] }
tokio_with_wasm = { version = "0.0.0", features = ["macros", "sync", "time", "rt"] }
Keep the feature lists of the two dependencies in sync. tokio's features
serve native platforms, and tokio_with_wasm's features enable the web glue.
Here's a simple example of using tokio_with_wasm that works on both native platforms and web browsers:
use tokio::task::{spawn, spawn_blocking, yield_now, JoinSet};
use tokio::time::{interval, sleep};
use tokio_with_wasm::alias as tokio;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let async_join_handle = spawn(async {
// Asynchronous code here.
// This will run concurrently
// in the same web worker (thread).
});
let blocking_join_handle = spawn_blocking(|| {
// Blocking code here.
// This will run in parallel
// 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.
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, allowing for shorter code.
#[cfg(all(
target_family = "wasm",
target_vendor = "unknown",
target_os = "unknown"
))]
use tokio_with_wasm as tokio;
#[cfg(not(all(
target_family = "wasm",
target_vendor = "unknown",
target_os = "unknown"
)))]
use tokio;
API documentation can be found on docs.rs.
Stick to the Result enum whenever possible.
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 Promises.
A panic inside spawn_blocking takes down the web worker that runs it. The
JoinHandle resolves to a JoinError whose is_panic is true, but the panic
payload is lost and the worker's share of the shared memory is never reclaimed.
If you're using Web Workers (threads) by calling spawn_blocking, you need to set specific Rust compiler flags. Also, you must use the nightly toolchain and include certain Rust standard library components in the compilation.
target-feature flags
+atomics+bulk-memory+mutable-globalslink-arg flags
--shared-memory--max-memory=1073741824--import-memory--export=__wasm_init_tls--export=__tls_size--export=__tls_align--export=__tls_basebuild-std components
stdpanic_abortHere's a full example command:
export RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals -C link-arg=--shared-memory -C link-arg=--max-memory=1073741824 -C link-arg=--import-memory -C link-arg=--export=__wasm_init_tls -C link-arg=--export=__tls_size -C link-arg=--export=__tls_align -C link-arg=--export=__tls_base"
export RUSTUP_TOOLCHAIN="nightly"
wasm-pack build <path> --target web -- -Z build-std=std,panic_abort
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. These headers let clients of your website access the SharedArrayBuffer web API, which is the web's counterpart to shared memory.
cross-origin-opener-policy: same-origincross-origin-embedder-policy: require-corp.wasm with the right MIME typeDon't forget to specify the MIME type application/wasm for .wasm files in your HTTP server configuration to ensure optimal performance.
WebAssembly.instantiateStreaming() rejects any other MIME type, so the module falls back to being compiled after the whole download finishes rather than while it arrives.
spawn_blocking runs its web workers from a blob: script by default. Under a
content security policy that forbids blob: workers, such as a browser
extension's script-src 'self', serve
blocking_worker.js
as your own file and point the pool at it:
tokio_with_wasm::only_web::set_worker_script_provider(|| Ok("/blocking_worker.js".into()));
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. That's why tokio doesn't really work well in 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 the 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.
Rust code can be called in a web worker. Therefore, we cannot access the global window JavaScript object
as we can on 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 the various capabilities of modern JavaScript.
Please note that this library uses quite a hacky and naive approach to mimic native tokio functionalities. That's because this library is meant as a temporary solution for the period before wasm32-wasi. Any kind of PR is welcome, as long as it makes things just work on the web.
Rust
98.2%
JavaScript
1.5%