use_resource.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #![allow(missing_docs)]
  2. use crate::{use_callback, use_signal, UseCallback};
  3. use dioxus_core::prelude::*;
  4. use dioxus_signals::*;
  5. use futures_util::{future, pin_mut, FutureExt, StreamExt};
  6. use std::ops::Deref;
  7. use std::{cell::Cell, future::Future, rc::Rc};
  8. #[doc = include_str!("../docs/use_resource.md")]
  9. #[doc = include_str!("../docs/rules_of_hooks.md")]
  10. #[doc = include_str!("../docs/moving_state_around.md")]
  11. #[doc(alias = "use_async_memo")]
  12. #[doc(alias = "use_memo_async")]
  13. #[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
  14. #[track_caller]
  15. pub fn use_resource<T, F>(mut future: impl FnMut() -> F + 'static) -> Resource<T>
  16. where
  17. T: 'static,
  18. F: Future<Output = T> + 'static,
  19. {
  20. let location = std::panic::Location::caller();
  21. let mut value = use_signal(|| None);
  22. let mut state = use_signal(|| UseResourceState::Pending);
  23. let (rc, changed) = use_hook(|| {
  24. let (rc, changed) = ReactiveContext::new_with_origin(location);
  25. (rc, Rc::new(Cell::new(Some(changed))))
  26. });
  27. let cb = use_callback(move || {
  28. // Create the user's task
  29. let fut = rc.reset_and_run_in(&mut future);
  30. // Spawn a wrapper task that polls the inner future and watch its dependencies
  31. spawn(async move {
  32. // move the future here and pin it so we can poll it
  33. let fut = fut;
  34. pin_mut!(fut);
  35. // Run each poll in the context of the reactive scope
  36. // This ensures the scope is properly subscribed to the future's dependencies
  37. let res = future::poll_fn(|cx| {
  38. rc.run_in(|| {
  39. tracing::trace_span!("polling resource", location = %location)
  40. .in_scope(|| fut.poll_unpin(cx))
  41. })
  42. })
  43. .await;
  44. // Set the value and state
  45. state.set(UseResourceState::Ready);
  46. value.set(Some(res));
  47. })
  48. });
  49. let mut task = use_hook(|| Signal::new(cb()));
  50. use_hook(|| {
  51. let mut changed = changed.take().unwrap();
  52. spawn(async move {
  53. loop {
  54. // Wait for the dependencies to change
  55. let _ = changed.next().await;
  56. // Stop the old task
  57. task.write().cancel();
  58. // Start a new task
  59. task.set(cb());
  60. }
  61. })
  62. });
  63. Resource {
  64. task,
  65. value,
  66. state,
  67. callback: cb,
  68. }
  69. }
  70. /// A handle to a reactive future spawned with [`use_resource`] that can be used to modify or read the result of the future.
  71. ///
  72. /// ## Example
  73. ///
  74. /// Reading the result of a resource:
  75. /// ```rust, no_run
  76. /// # use dioxus::prelude::*;
  77. /// # use std::time::Duration;
  78. /// fn App() -> Element {
  79. /// let mut revision = use_signal(|| "1d03b42");
  80. /// let mut resource = use_resource(move || async move {
  81. /// // This will run every time the revision signal changes because we read the count inside the future
  82. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  83. /// });
  84. ///
  85. /// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
  86. /// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
  87. /// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
  88. /// match &*resource.read_unchecked() {
  89. /// Some(Ok(value)) => rsx! { "{value:?}" },
  90. /// Some(Err(err)) => rsx! { "Error: {err}" },
  91. /// None => rsx! { "Loading..." },
  92. /// }
  93. /// }
  94. /// ```
  95. #[derive(Debug)]
  96. pub struct Resource<T: 'static> {
  97. value: Signal<Option<T>>,
  98. task: Signal<Task>,
  99. state: Signal<UseResourceState>,
  100. callback: UseCallback<Task>,
  101. }
  102. impl<T> PartialEq for Resource<T> {
  103. fn eq(&self, other: &Self) -> bool {
  104. self.value == other.value
  105. && self.state == other.state
  106. && self.task == other.task
  107. && self.callback == other.callback
  108. }
  109. }
  110. impl<T> Clone for Resource<T> {
  111. fn clone(&self) -> Self {
  112. *self
  113. }
  114. }
  115. impl<T> Copy for Resource<T> {}
  116. /// A signal that represents the state of the resource
  117. // we might add more states (panicked, etc)
  118. #[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
  119. pub enum UseResourceState {
  120. /// The resource's future is still running
  121. Pending,
  122. /// The resource's future has been forcefully stopped
  123. Stopped,
  124. /// The resource's future has been paused, tempoarily
  125. Paused,
  126. /// The resource's future has completed
  127. Ready,
  128. }
  129. impl<T> Resource<T> {
  130. /// Restart the resource's future.
  131. ///
  132. /// This will cancel the current future and start a new one.
  133. ///
  134. /// ## Example
  135. /// ```rust, no_run
  136. /// # use dioxus::prelude::*;
  137. /// # use std::time::Duration;
  138. /// fn App() -> Element {
  139. /// let mut revision = use_signal(|| "1d03b42");
  140. /// let mut resource = use_resource(move || async move {
  141. /// // This will run every time the revision signal changes because we read the count inside the future
  142. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  143. /// });
  144. ///
  145. /// rsx! {
  146. /// button {
  147. /// // We can get a signal with the value of the resource with the `value` method
  148. /// onclick: move |_| resource.restart(),
  149. /// "Restart resource"
  150. /// }
  151. /// "{resource:?}"
  152. /// }
  153. /// }
  154. /// ```
  155. pub fn restart(&mut self) {
  156. self.task.write().cancel();
  157. let new_task = self.callback.call();
  158. self.task.set(new_task);
  159. }
  160. /// Forcefully cancel the resource's future.
  161. ///
  162. /// ## Example
  163. /// ```rust, no_run
  164. /// # use dioxus::prelude::*;
  165. /// # use std::time::Duration;
  166. /// fn App() -> Element {
  167. /// let mut revision = use_signal(|| "1d03b42");
  168. /// let mut resource = use_resource(move || async move {
  169. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  170. /// });
  171. ///
  172. /// rsx! {
  173. /// button {
  174. /// // We can cancel the resource before it finishes with the `cancel` method
  175. /// onclick: move |_| resource.cancel(),
  176. /// "Cancel resource"
  177. /// }
  178. /// "{resource:?}"
  179. /// }
  180. /// }
  181. /// ```
  182. pub fn cancel(&mut self) {
  183. self.state.set(UseResourceState::Stopped);
  184. self.task.write().cancel();
  185. }
  186. /// Pause the resource's future.
  187. ///
  188. /// ## Example
  189. /// ```rust, no_run
  190. /// # use dioxus::prelude::*;
  191. /// # use std::time::Duration;
  192. /// fn App() -> Element {
  193. /// let mut revision = use_signal(|| "1d03b42");
  194. /// let mut resource = use_resource(move || async move {
  195. /// // This will run every time the revision signal changes because we read the count inside the future
  196. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  197. /// });
  198. ///
  199. /// rsx! {
  200. /// button {
  201. /// // We can pause the future with the `pause` method
  202. /// onclick: move |_| resource.pause(),
  203. /// "Pause"
  204. /// }
  205. /// button {
  206. /// // And resume it with the `resume` method
  207. /// onclick: move |_| resource.resume(),
  208. /// "Resume"
  209. /// }
  210. /// "{resource:?}"
  211. /// }
  212. /// }
  213. /// ```
  214. pub fn pause(&mut self) {
  215. self.state.set(UseResourceState::Paused);
  216. self.task.write().pause();
  217. }
  218. /// Resume the resource's future.
  219. ///
  220. /// ## Example
  221. /// ```rust, no_run
  222. /// # use dioxus::prelude::*;
  223. /// # use std::time::Duration;
  224. /// fn App() -> Element {
  225. /// let mut revision = use_signal(|| "1d03b42");
  226. /// let mut resource = use_resource(move || async move {
  227. /// // This will run every time the revision signal changes because we read the count inside the future
  228. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  229. /// });
  230. ///
  231. /// rsx! {
  232. /// button {
  233. /// // We can pause the future with the `pause` method
  234. /// onclick: move |_| resource.pause(),
  235. /// "Pause"
  236. /// }
  237. /// button {
  238. /// // And resume it with the `resume` method
  239. /// onclick: move |_| resource.resume(),
  240. /// "Resume"
  241. /// }
  242. /// "{resource:?}"
  243. /// }
  244. /// }
  245. /// ```
  246. pub fn resume(&mut self) {
  247. if self.finished() {
  248. return;
  249. }
  250. self.state.set(UseResourceState::Pending);
  251. self.task.write().resume();
  252. }
  253. /// Clear the resource's value. This will just reset the value. It will not modify any running tasks.
  254. ///
  255. /// ## Example
  256. /// ```rust, no_run
  257. /// # use dioxus::prelude::*;
  258. /// # use std::time::Duration;
  259. /// fn App() -> Element {
  260. /// let mut revision = use_signal(|| "1d03b42");
  261. /// let mut resource = use_resource(move || async move {
  262. /// // This will run every time the revision signal changes because we read the count inside the future
  263. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  264. /// });
  265. ///
  266. /// rsx! {
  267. /// button {
  268. /// // We clear the value without modifying any running tasks with the `clear` method
  269. /// onclick: move |_| resource.clear(),
  270. /// "Clear"
  271. /// }
  272. /// "{resource:?}"
  273. /// }
  274. /// }
  275. /// ```
  276. pub fn clear(&mut self) {
  277. self.value.write().take();
  278. }
  279. /// Get a handle to the inner task backing this resource
  280. /// Modify the task through this handle will cause inconsistent state
  281. pub fn task(&self) -> Task {
  282. self.task.cloned()
  283. }
  284. /// Is the resource's future currently finished running?
  285. ///
  286. /// Reading this does not subscribe to the future's state
  287. ///
  288. /// ## Example
  289. /// ```rust, no_run
  290. /// # use dioxus::prelude::*;
  291. /// # use std::time::Duration;
  292. /// fn App() -> Element {
  293. /// let mut revision = use_signal(|| "1d03b42");
  294. /// let mut resource = use_resource(move || async move {
  295. /// // This will run every time the revision signal changes because we read the count inside the future
  296. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  297. /// });
  298. ///
  299. /// // We can use the `finished` method to check if the future is finished
  300. /// if resource.finished() {
  301. /// rsx! {
  302. /// "The resource is finished"
  303. /// }
  304. /// } else {
  305. /// rsx! {
  306. /// "The resource is still running"
  307. /// }
  308. /// }
  309. /// }
  310. /// ```
  311. pub fn finished(&self) -> bool {
  312. matches!(
  313. *self.state.peek(),
  314. UseResourceState::Ready | UseResourceState::Stopped
  315. )
  316. }
  317. /// Get the current state of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current state of the resource or passed to other hooks and components.
  318. ///
  319. /// ## Example
  320. /// ```rust, no_run
  321. /// # use dioxus::prelude::*;
  322. /// # use std::time::Duration;
  323. /// fn App() -> Element {
  324. /// let mut revision = use_signal(|| "1d03b42");
  325. /// let mut resource = use_resource(move || async move {
  326. /// // This will run every time the revision signal changes because we read the count inside the future
  327. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  328. /// });
  329. ///
  330. /// // We can read the current state of the future with the `state` method
  331. /// match resource.state().cloned() {
  332. /// UseResourceState::Pending => rsx! {
  333. /// "The resource is still pending"
  334. /// },
  335. /// UseResourceState::Paused => rsx! {
  336. /// "The resource has been paused"
  337. /// },
  338. /// UseResourceState::Stopped => rsx! {
  339. /// "The resource has been stopped"
  340. /// },
  341. /// UseResourceState::Ready => rsx! {
  342. /// "The resource is ready!"
  343. /// },
  344. /// }
  345. /// }
  346. /// ```
  347. pub fn state(&self) -> ReadOnlySignal<UseResourceState> {
  348. self.state.into()
  349. }
  350. /// Get the current value of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current value of the resource or passed to other hooks and components.
  351. ///
  352. /// ## Example
  353. ///
  354. /// ```rust, no_run
  355. /// # use dioxus::prelude::*;
  356. /// # use std::time::Duration;
  357. /// fn App() -> Element {
  358. /// let mut revision = use_signal(|| "1d03b42");
  359. /// let mut resource = use_resource(move || async move {
  360. /// // This will run every time the revision signal changes because we read the count inside the future
  361. /// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
  362. /// });
  363. ///
  364. /// // We can get a signal with the value of the resource with the `value` method
  365. /// let value = resource.value();
  366. ///
  367. /// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
  368. /// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
  369. /// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
  370. /// match &*value.read_unchecked() {
  371. /// Some(Ok(value)) => rsx! { "{value:?}" },
  372. /// Some(Err(err)) => rsx! { "Error: {err}" },
  373. /// None => rsx! { "Loading..." },
  374. /// }
  375. /// }
  376. /// ```
  377. pub fn value(&self) -> ReadOnlySignal<Option<T>> {
  378. self.value.into()
  379. }
  380. /// Suspend the resource's future and only continue rendering when the future is ready
  381. pub fn suspend(&self) -> std::result::Result<MappedSignal<T>, RenderError> {
  382. match self.state.cloned() {
  383. UseResourceState::Stopped | UseResourceState::Paused | UseResourceState::Pending => {
  384. let task = self.task();
  385. if task.paused() {
  386. Ok(self.value.map(|v| v.as_ref().unwrap()))
  387. } else {
  388. Err(RenderError::Suspended(SuspendedFuture::new(task)))
  389. }
  390. }
  391. _ => Ok(self.value.map(|v| v.as_ref().unwrap())),
  392. }
  393. }
  394. }
  395. impl<T> From<Resource<T>> for ReadOnlySignal<Option<T>> {
  396. fn from(val: Resource<T>) -> Self {
  397. val.value.into()
  398. }
  399. }
  400. impl<T> Readable for Resource<T> {
  401. type Target = Option<T>;
  402. type Storage = UnsyncStorage;
  403. #[track_caller]
  404. fn try_read_unchecked(
  405. &self,
  406. ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
  407. self.value.try_read_unchecked()
  408. }
  409. #[track_caller]
  410. fn try_peek_unchecked(
  411. &self,
  412. ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
  413. self.value.try_peek_unchecked()
  414. }
  415. }
  416. impl<T> IntoAttributeValue for Resource<T>
  417. where
  418. T: Clone + IntoAttributeValue,
  419. {
  420. fn into_value(self) -> dioxus_core::AttributeValue {
  421. self.with(|f| f.clone().into_value())
  422. }
  423. }
  424. impl<T> IntoDynNode for Resource<T>
  425. where
  426. T: Clone + IntoDynNode,
  427. {
  428. fn into_dyn_node(self) -> dioxus_core::DynamicNode {
  429. self().into_dyn_node()
  430. }
  431. }
  432. /// Allow calling a signal with signal() syntax
  433. ///
  434. /// Currently only limited to copy types, though could probably specialize for string/arc/rc
  435. impl<T: Clone> Deref for Resource<T> {
  436. type Target = dyn Fn() -> Option<T>;
  437. fn deref(&self) -> &Self::Target {
  438. Readable::deref_impl(self)
  439. }
  440. }