How does `fetch` work?

How does fetch work?

Is the question asked on the Seed chat. The answer isn’t in the official guides in seed-rs.org yet, so I’ll try to explain it here.

Let’s look at the code first. This is a part of server_integration example:

pub enum Msg {
    ...
    SendRequest,
    Fetched(fetch::Result<shared::SendMessageResponseBody>),
}

pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
    match msg {
        ...
        Msg::SendRequest => {
            orders.skip().perform_cmd({
                let message = model.new_message.clone();
                async { Msg::Fetched(send_message(message).await) }
            });
        }

        Msg::Fetched(Ok(response_data)) => {
            ...
        }

        Msg::Fetched(Err(fetch_error)) => {
           ...
        }
    }
}

async fn send_message(new_message: String) -> fetch::Result<shared::SendMessageResponseBody> {
    Request::new(get_request_url())
        .method(Method::Post)
        .json(&shared::SendMessageRequestBody { text: new_message })?
        .fetch()
        .await?
        .check_status()?
        .json()
        .await
}

Individual parts with explanations:


pub enum Msg {
    ...
    SendRequest,
    Fetched(fetch::Result<shared::SendMessageResponseBody>),
}

fetch::Result is just an alias for Result<T, FetchError>. T is a custom type shared::SendMessageResponseBody.


        Msg::Fetched(Ok(response_data)) => {
            ...
        }

        Msg::Fetched(Err(fetch_error)) => {
           ...
        }

fetch::Result is the enum with variants Ok(T) and Err(FetchError). We can handle each variant by a dedicated match arm to eliminate nesting and improve readability.


        Msg::SendRequest => {
            orders.skip().perform_cmd({
                let message = model.new_message.clone();
                async { Msg::Fetched(send_message(message).await) }
            });
        }
  • skip() isn’t required - we know that we don’t modify Model at all so we can tell Seed that it doesn’t have to rerender page - i.e. it can skip rendering. .skip() is just a simple performance optimization.
  • async functions/blocks are executed sometime in the future. That’s why they often accept only owned values. In our case we need to clone model.new_message because it’s possible that it will be mutated before our async block is executed and compiler doesn’t allow this potentially harmful behavior.
  • Stable Rust supports only async blocks and functions. async closures aren’t supported yet.
  • orders.perform_cmd expects a Future as the argument. Then it executes the Future (by converting to Javascript Promise) and invokes app’s update function with the Msg returned from the Future (if any).
  • async blocks are basically Futures.
  • send_message is an async function - it returns a Future so we have to use .await to “unwrap” its inner value to match the type defined in Msg::Fetched - fetch::Result<shared::SendMessageResponseBody>.
  • We can’t make update async and just “await” the async operations because it would block the render loop and GUI would be frozen until the awaited Future/Promise is resolved.

async fn send_message(new_message: String) -> fetch::Result<shared::SendMessageResponseBody> {
    Request::new(get_request_url())  // Prepare the request to the selected URL.
        .method(Method::Post)   // POST (default is GET)
        .json(&shared::SendMessageRequestBody { text: new_message })?   // Serialize payload to JSON. Serialization can fail and return `FetchError`.
        .fetch()   // Send the request.
        .await?   // Wait for the response. Request can fail and return `FetchError`.
        .check_status()?  // Make sure the response status is 2xx. Otherwise return `FetchError`.
        .json()   // Deserialize JSON to the required type. Rust is clever enough to know that it should deserialize to the return value type wrapped in `Result` - `shared::SendMessageResponseBody`.
        .await  // Wait for deserialization. It can fail and return `FetchError`.
}

See comments in the code above.
Note: ? means early return on error.

1 Like