Note: This chapter only explains Seed fetch API, there are no Time Tracker changes.
How does Seed
fetchwork?
Seed fetch is basically a thin wrapper around Fetch API.
Let's look at the code first. This is a part of the 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 response data type (shared::SendMessageResponseBody in our case).
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, but 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.async blocks are basically Futures.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).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>.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.
Let's look at the code from the previous chapter:
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
    orders
        ....
        .perform_cmd(async { 
            Msg::AuthConfigFetched(
                async { fetch("/auth_config.json").await?.check_status()?.json().await }.await
            )
        });
...
#[derive(Deserialize)]
struct AuthConfig {
    domain: String,
    client_id: String,
}
...
enum Msg {
    ...
    AuthConfigFetched(fetch::Result<AuthConfig>),
    
...
fn update(...) {
    match msg {
        ...
        Msg::HideMenu => {
            ...
        },
        Msg::AuthConfigFetched(Ok(auth_config)) => model.auth_config = Some(auth_config),
        Msg::AuthConfigFetched(Err(fetch_error)) => error!("AuthConfig fetch failed!", fetch_error),
        ...
    }
The structure is pretty similar to the previous Example A, so let's focus only on this part:
.perform_cmd(async { 
    Msg::AuthConfigFetched(
        async { fetch("/auth_config.json").await?.check_status()?.json().await }.await
    )
});
fetch function has this type:
pub async fn fetch<'a>(request: impl Into<Request<'a>>) -> Result<Response>
It means fetch is basically a shortcut for Request::new(...).fetch() (see the previous example).
And impl Into<Request<'a>> allows us to pass different items as the argument. From for Request is currently implemented for:
impl<'a, T: Into<Cow<'a, str>>> From<T> for Request<'a> {
    ...
} // => it allows to pass `String`, `&str`, `Cow<str>`, etc.
impl<'a> From<Url> for Request<'a> {
    ...
}
I hope you learned something about fetch and async/.await.
Other links related to this topic:
docs.rs.We'll return back to Time Tracker app and we'll try to explore Slash GraphQL in the next chapter.