Fetch

Introduction

The fetch service can be used to make HTTP requests to a server. This enables applications to communicate with external services.

Fetch - 图1note

You might find it helpful to read the documentation for the format module before reading this page.

Making requests

Building requests

Yew re-exports the Request struct from the http crate that is used to ‘build’ requests before they can be dispatched to a server. The value of the request body must implement Into<Text> or Into<Binary>.

Text and Binary are type aliases for the following Result types:

  1. pub type Text = Result<String, Error>;
  2. pub type Binary = Result<Vec<u8>, Error>;

Here is what a typical GET request will look like:

  1. use yew::format::Nothing;
  2. use yew::services::fetch::Request;
  3. let get_request = Request::get("https://example.com/api/v1/get/something")
  4. .body(Nothing)
  5. .expect("Could not build that request");

Here is what a typical POST request will look like:

  1. use serde_json::json;
  2. use yew::format::Json;
  3. use yew::services::fetch::Request;
  4. let post_request = Request::post("https://example.com/api/v1/post/something")
  5. .header("Content-Type", "application/json")
  6. .body(Json(&json!({"key": "value"})))
  7. .expect("Could not build that request.");

Fetch - 图2note

Note that the structs in the format module take references to values instead of values (i.e. &T not T).

Dispatching requests

The FetchService provides a binding to the browser’s fetch API. Requests can be sent using either FetchService::fetch or FetchService::fetch_with_options (fetch_with_options should be used when cookies need to be sent with a request).

FetchService::fetch accepts two parameters: a Request object and a Callback. The Callback is called once the request has completed allowing you to handle the data returned from the request. The callback you pass needs to take a single parameter of type Response<T> where T is the body of the response. Yew needs to be able to parse the response body to create an instance of the data type T so it needs to implement From<Text> or From<Binary>. To fetch data in a binary format you should use FetchService::fetch_binary rather than FetchService::fetch.

Fetch - 图3note

Because something could go wrong trying to deserialize data From<Text> and From<Binary> are only implemented for FormatDataType<Result<T, ::anyhow::Error>> (not FormatDataType<T>) where FormatDataType is used as a placeholder for any type in the format module (e.g. Json).

This means that your callbacks should look like

  1. use yew::format::Json;
  2. self.link.callback(|response: Json<anyhow::Result<ResponseType>>|)

rather than

  1. use yew::format::Json;
  2. self.link.callback(|response: Json<ResponseType>|)

Fetch - 图4caution

If the FetchTask is dropped before the request has finished, it will be cancelled. Make sure to keep it around!

Fetch - 图5info

If you keep getting an error saying that “the operation was aborted” or “Error 408” this might be because the CORS headers of the website you are trying to access are not set correctly. Please see the linked article from Mozilla about how to resolve these errors.

An illustrated example of how to fetch data from an API giving information about the ISS’s (International Space Station) location is given below.

  1. // requires the serde and anyhow crates
  2. use serde::Deserialize;
  3. use yew::{
  4. format::{Json, Nothing},
  5. prelude::*,
  6. services::fetch::{FetchService, FetchTask, Request, Response},
  7. };
  8. #[derive(Deserialize, Debug, Clone)]
  9. pub struct ISSPosition {
  10. latitude: String,
  11. longitude: String,
  12. }
  13. #[derive(Deserialize, Debug, Clone)]
  14. pub struct ISS {
  15. message: String,
  16. timestamp: i32,
  17. iss_position: ISSPosition,
  18. }
  19. #[derive(Debug)]
  20. pub enum Msg {
  21. GetLocation,
  22. ReceiveResponse(Result<ISS, anyhow::Error>),
  23. }
  24. #[derive(Debug)]
  25. pub struct FetchServiceExample {
  26. fetch_task: Option<FetchTask>,
  27. iss: Option<ISS>,
  28. link: ComponentLink<Self>,
  29. error: Option<String>,
  30. }
  31. /// Some of the code to render the UI is split out into smaller functions here to make the code
  32. /// cleaner and show some useful design patterns.
  33. impl FetchServiceExample {
  34. fn view_iss_location(&self) -> Html {
  35. match self.iss {
  36. Some(ref space_station) => {
  37. html! {
  38. <>
  39. <p>{ "The ISS is at:" }</p>
  40. <p>{ format!("Latitude: {}", space_station.iss_position.latitude) }</p>
  41. <p>{ format!("Longitude: {}", space_station.iss_position.longitude) }</p>
  42. </>
  43. }
  44. }
  45. None => {
  46. html! {
  47. <button onclick=self.link.callback(|_| Msg::GetLocation)>
  48. { "Where is the ISS?" }
  49. </button>
  50. }
  51. }
  52. }
  53. }
  54. fn view_fetching(&self) -> Html {
  55. if self.fetch_task.is_some() {
  56. html! { <p>{ "Fetching data..." }</p> }
  57. } else {
  58. html! { <p></p> }
  59. }
  60. }
  61. fn view_error(&self) -> Html {
  62. if let Some(ref error) = self.error {
  63. html! { <p>{ error.clone() }</p> }
  64. } else {
  65. html! {}
  66. }
  67. }
  68. }
  69. impl Component for FetchServiceExample {
  70. type Message = Msg;
  71. type Properties = ();
  72. fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
  73. Self {
  74. fetch_task: None,
  75. iss: None,
  76. link,
  77. error: None,
  78. }
  79. }
  80. fn change(&mut self, _props: Self::Properties) -> bool {
  81. false
  82. }
  83. fn update(&mut self, msg: Self::Message) -> bool {
  84. use Msg::*;
  85. match msg {
  86. GetLocation => {
  87. // 1. build the request
  88. let request = Request::get("http://api.open-notify.org/iss-now.json")
  89. .body(Nothing)
  90. .expect("Could not build request.");
  91. // 2. construct a callback
  92. let callback =
  93. self.link
  94. .callback(|response: Response<Json<Result<ISS, anyhow::Error>>>| {
  95. let Json(data) = response.into_body();
  96. Msg::ReceiveResponse(data)
  97. });
  98. // 3. pass the request and callback to the fetch service
  99. let task = FetchService::fetch(request, callback).expect("failed to start request");
  100. // 4. store the task so it isn't canceled immediately
  101. self.fetch_task = Some(task);
  102. // we want to redraw so that the page displays a 'fetching...' message to the user
  103. // so return 'true'
  104. true
  105. }
  106. ReceiveResponse(response) => {
  107. match response {
  108. Ok(location) => {
  109. self.iss = Some(location);
  110. }
  111. Err(error) => {
  112. self.error = Some(error.to_string())
  113. }
  114. }
  115. self.fetch_task = None;
  116. // we want to redraw so that the page displays the location of the ISS instead of
  117. // 'fetching...'
  118. true
  119. }
  120. }
  121. }
  122. fn view(&self) -> Html {
  123. html! {
  124. <>
  125. { self.view_fetching() }
  126. { self.view_iss_location() }
  127. { self.view_error() }
  128. </>
  129. }
  130. }
  131. }

Debugging the FetchService

Most browsers’ developer tools have a “network” panel which can be used to inspect HTTP requests. This can be used to gain insight into what requests are being made, the data being sent to the server, as well as the response.

The Rust Wasm Book also contains useful debugging tips for Wasm applications.

Further reading