Course contentsShow
← Rust
Lesson 1837 of 1,91016. Specialized Runtime TargetsFree lesson

Asynchronous Operations in Wasm

Integrate Rust futures with JavaScript event loops to perform non-blocking I/O in browser environments.

Bridging Rust Futures and the JavaScript Event Loop

In this lesson, you will learn how to orchestrate Rust’s Future trait to perform non-blocking I/O by hooking into the browser’s native JavaScript event loop.

When running Rust in Wasm, we lose the concept of a multi-threaded OS kernel that handles blocking I/O. Because the browser is inherently single-threaded, if a Rust function blocks the execution thread, the entire browser tab freezes. To perform non-blocking I/O—like fetching a resource or waiting for a timer—we must map our Rust Future state machine to the JavaScript Promise infrastructure.

The Mechanism of Integration

Rust’s Future model is poll-based. A Future does nothing unless it is polled by an executor. In the browser, we use the wasm-bindgen and js-sys crates to hand off the responsibility of "waking" the task to the JavaScript host.

When you initiate an asynchronous operation in Rust, you are essentially creating a Future that eventually calls into a JavaScript API (like fetch). The JavaScript runtime manages the underlying I/O. Once the operation completes, the browser places a callback on the event loop. Our executor must ensure that this callback triggers the Waker associated with our Rust Future.

Here is a conceptual look at how we bridge these worlds:

// A hypothetical non-blocking sleep using the JS event loop
pub async fn sleep_in_browser(ms: u32) {
    let promise = js_sys::Promise::new(&mut |resolve, _| {
        web_sys::window()
            .unwrap()
            .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms as i32)
            .unwrap();
    });

    // We convert the JS Promise into a Rust Future
    wasm_bindgen_futures::JsFuture::from(promise).await.unwrap();
}

By await-ing the JsFuture, we yield control back to the browser’s event loop. This prevents the Wasm instance from halting execution, allowing the browser to paint frames and handle user input while the I/O operation is "in flight." When the timer fires, the JavaScript resolve function triggers, which internally signals the Waker to poll our Rust task again, allowing it to proceed from its suspended state.

Key Takeaway: To perform non-blocking I/O in Wasm, we map Rust's poll-based futures to JavaScript Promises, allowing the browser's event loop to manage execution and wake the task upon completion.