Skip to main content

fusor_async/
browser.rs

1//! Browser executor and an abortable GET adapter. HTTP caching follows Fetch defaults.
2use crate::{RequestContext, Resource};
3use fusor::OwnerHandle;
4use std::future::Future;
5use wasm_bindgen::{JsCast, JsValue};
6use wasm_bindgen_futures::JsFuture;
7use web_sys::{AbortController, Request, RequestInit, Response};
8
9/// Declare a read participating in the nearest generated coherent region.
10pub fn read<K, T, E, F>(
11    owner: &OwnerHandle,
12    key: impl Fn() -> K + 'static,
13    load: impl Fn(K, RequestContext) -> F + 'static,
14) -> crate::AsyncValue<K, T, E>
15where
16    K: Clone + PartialEq + 'static,
17    T: 'static,
18    E: std::fmt::Display + 'static,
19    F: Future<Output = Result<T, E>> + 'static,
20{
21    crate::AsyncValue::new(owner, key, load, wasm_bindgen_futures::spawn_local)
22}
23
24pub fn resource<K, T, E, F>(
25    owner: &OwnerHandle,
26    key: impl Fn() -> Option<K> + 'static,
27    load: impl Fn(K, RequestContext) -> F + 'static,
28) -> Resource<K, T, E>
29where
30    K: Clone + PartialEq + 'static,
31    T: 'static,
32    E: 'static,
33    F: Future<Output = Result<T, E>> + 'static,
34{
35    Resource::new(owner, key, load, wasm_bindgen_futures::spawn_local)
36}
37
38impl RequestContext {
39    /// GET and fully read a UTF-8 response body. Rejects non-2xx responses.
40    /// The AbortController stays registered through body consumption.
41    pub async fn get_text(&self, url: &str) -> Result<String, JsValue> {
42        if self.is_cancelled() {
43            return Err(JsValue::from_str("request cancelled"));
44        }
45        let controller = AbortController::new()?;
46        let options = RequestInit::new();
47        options.set_method("GET");
48        options.set_signal(Some(&controller.signal()));
49        let request = Request::new_with_str_and_init(url, &options)?;
50        let _cancel = self.on_cancel(move || controller.abort());
51        let window =
52            web_sys::window().ok_or_else(|| JsValue::from_str("Fetch requires a browser"))?;
53        let response: Response = JsFuture::from(window.fetch_with_request(&request))
54            .await?
55            .dyn_into()?;
56        if !response.ok() {
57            return Err(JsValue::from_str(&format!(
58                "GET {url}: HTTP {}",
59                response.status()
60            )));
61        }
62        JsFuture::from(response.text()?)
63            .await?
64            .as_string()
65            .ok_or_else(|| JsValue::from_str("Fetch returned a non-text body"))
66    }
67}