DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

async-operations-in-euv

Async Operations in euv

Project Code:https://github.com/euv-dev/euv

Modern web applications are inherently asynchronous. Whether you are fetching data from an API, reading files from the user's device, or waiting for a timer to expire, your code needs to handle operations that complete at some point in the future. euv provides a robust set of async primitives — including spawn_local, JsFuture, JSON handling, Promises, and browser Response objects — that make it possible to write asynchronous Rust code for the web. This article explores all the async capabilities available in euv.

Understanding Async in the Browser Context

In a traditional Rust application, you might use tokio or async-stt for asynchronous operations. However, euv applications run in the browser via WebAssembly (WASM), which has a single-threaded event loop. Instead of a multi-threaded runtime, euv leverages the browser's native async primitives — Promises, fetch, and callbacks — and wraps them in Rust-friendly APIs.

The key async primitives in euv are:

  • spawn_local: Spawns an asynchronous task on the browser's event loop.
  • JsFuture: Wraps a JavaScript Promise into a Rust Future.
  • JSON utilities: Parse and serialize JSON data from JavaScript.
  • Promise / Response: Direct access to browser async APIs.

spawn_local — Spawning Async Tasks

spawn_local is the primary way to start an asynchronous task in euv. It schedules a future to run on the browser's event loop, similar to how tokio::spawn works in server-side Rust.

spawn_local(async {
    // This code runs asynchronously
    let result = some_async_operation().await;
    // Handle the result
});
Enter fullscreen mode Exit fullscreen mode

spawn_local takes an async block (or any type that implements Future) and schedules it for execution. The future will be polled by the browser's event loop, and when it completes, any .await points will resume.

Common use cases for spawn_local include:

  • Data fetching: Making HTTP requests to APIs without blocking the UI.
  • Timers: Scheduling delayed or periodic actions.
  • File reading: Reading user-selected files asynchronously.
  • Complex computations: Offloading work to avoid blocking the main thread.

JsFuture — Bridging JavaScript Promises to Rust

JsFuture is a wrapper that converts a JavaScript Promise into a Rust Future. This is the bridge between the JavaScript async world and Rust's async/await syntax.

When you call a browser API that returns a Promise (such as fetch), you can wrap the result in a JsFuture and .await it in Rust:

let future = JsFuture::from(some_js_promise());
let result = future.await;
Enter fullscreen mode Exit fullscreen mode

JsFuture implements the standard Rust Future trait, so it works seamlessly with Rust's async/await syntax and can be combined with other futures.

JSON Handling

euv provides utilities for working with JSON data, which is the lingua franca of web APIs. You can:

  • Parse JSON: Convert a JSON string from JavaScript into a Rust data structure.
  • Serialize to JSON: Convert a Rust data structure into a JSON string for sending to an API.

JSON handling is essential for any application that communicates with a backend server. Combined with fetch (via JsFuture), you can build complete data-fetching pipelines:

  1. Make an HTTP request using the browser's fetch API.
  2. Wrap the resulting Promise in a JsFuture.
  3. Await the response and extract JSON data.
  4. Parse the JSON into Rust types.

Promise and Response Objects

euv provides direct access to the browser's Promise and Response objects, enabling you to work with the full power of the browser's async APIs.

Promise

A Promise represents a value that will be available at some point in the future. In euv, you can create Promises, resolve them, and chain them with .then() and .catch().

Response

A Response represents the result of an HTTP request. It provides methods for accessing the response body in various formats:

  • .json(): Parse the response body as JSON.
  • .text(): Get the response body as a plain text string.
  • .array_buffer(): Get the response body as an ArrayBuffer for binary data.

When combined with JsFuture, you can await a Response and then extract the data:

let response = JsFuture::from(fetch(url)).await;
let json_data = JsFuture::from(response.json()).await;
Enter fullscreen mode Exit fullscreen mode

Fetching Data from APIs

One of the most common async operations in web applications is fetching data from REST APIs. In euv, this involves:

  1. Using the browser's fetch function to make an HTTP request.
  2. Wrapping the resulting Promise in a JsFuture.
  3. Awaiting the response and extracting the data.

Here is the general pattern:

spawn_local(async {
    let response = JsFuture::from(fetch("https://api.example.com/data")).await;
    // Process the response
    let data = JsFuture::from(response.json()).await;
    // Use the data to update signals
});
Enter fullscreen mode Exit fullscreen mode

The fetch function is available through the browser's Window object, which euv provides access to.

Error Handling in Async Code

Async operations can fail for many reasons: network errors, server errors, timeouts, malformed data, and more. Proper error handling is essential for a good user experience.

In euv, you handle async errors using Rust's standard error handling mechanisms:

  • Result: Most async operations return a Result type. Use match or the ? operator to handle success and failure cases.
  • unwrap_or_default(): For operations where a default value is acceptable on failure.
  • Signal updates: Update error state signals to display error messages in the UI.
spawn_local(async {
    let result = some_async_operation().await;
    match result {
        Ok(data) => {
            // Update success state
        }
        Err(error) => {
            // Update error state
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

Combining Multiple Async Operations

Often, you need to perform multiple async operations concurrently or in sequence. euv supports both patterns:

Sequential Execution

Simply .await each operation one after another:

let first = operation_one().await;
let second = operation_two(first).await;
Enter fullscreen mode Exit fullscreen mode

Concurrent Execution

Use Rust's join! macro or FuturesUnordered to run multiple futures concurrently:

let (result1, result2) = futures::join!(operation_one(), operation_two());
Enter fullscreen mode Exit fullscreen mode

Async and Reactive Signals

The real power of euv's async system emerges when you combine it with reactive signals. You can:

  1. Set a loading state signal before starting an async operation.
  2. Spawn the async task using spawn_local.
  3. Update data or error signals when the operation completes.
let shared_text: Signal<String> = use_signal(|| "Type here...".to_string());

spawn_local(async {
    // Perform async work
    let result = fetch_data().await;
    // Update the signal with the result
    shared_text.set(result);
});
Enter fullscreen mode Exit fullscreen mode

This pattern keeps your UI responsive while async operations are in progress, and automatically updates the UI when the data arrives.

Practical Example: Temperature Converter with Async Data

Consider a temperature converter that fetches the current temperature from a weather API:

watch!(celsius, |celsius_value: f64| {
    fahrenheit.set(celsius_value * 9.0 / 5.0 + 32.0);
});
Enter fullscreen mode Exit fullscreen mode

You could extend this pattern to fetch the initial temperature from an API:

spawn_local(async {
    let response = JsFuture::from(fetch("https://api.weather.com/temperature")).await;
    let data = JsFuture::from(response.json()).await;
    // Parse and set the initial celsius value
});
Enter fullscreen mode Exit fullscreen mode

The watch! macro ensures that whenever the celsius value changes (whether from the API or from user input), the fahrenheit value is automatically updated.

Summary

euv provides a comprehensive set of async primitives for building modern web applications:

  • spawn_local: Spawn async tasks on the browser's event loop.
  • JsFuture: Wrap JavaScript Promises as Rust Futures.
  • JSON utilities: Parse and serialize JSON data.
  • Promise / Response: Direct access to browser async APIs.
  • Error handling: Standard Rust error handling with Result, match, and ?.
  • Signal integration: Update reactive signals from async callbacks.

By mastering these async primitives, you can build euv applications that communicate with APIs, handle user files, and perform complex asynchronous operations — all while keeping the UI responsive and the code clean and maintainable.


Project Code:https://github.com/euv-dev/euv

Top comments (0)