From 46c78e5960cfdc268176298c8e02aca5f89e86b4 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Fri, 9 Feb 2018 22:52:23 +0300 Subject: [PATCH 1/7] Add ScopeHandle for component destroying --- src/html.rs | 33 ++++++++++++++++++++++++++++----- src/services/interval.rs | 8 ++++++++ src/virtual_dom/vcomp.rs | 20 +++++++++++++++----- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/html.rs b/src/html.rs index b184b7467..bcb1b0c9f 100644 --- a/src/html.rs +++ b/src/html.rs @@ -294,7 +294,7 @@ where COMP: Component + Renderable, { /// Alias to `mount("body", ...)`. - pub fn mount_to_body(self) { + pub fn mount_to_body(self) -> ScopeHandle { let element = document().query_selector("body") .expect("can't get body node for rendering"); self.mount(element) @@ -304,14 +304,14 @@ where /// function in Elm. You should provide an initial model, `update` function /// which will update the state of the model and a `view` function which /// will render the model to a virtual DOM tree. - pub fn mount(self, element: Element) { + pub fn mount(self, element: Element) -> ScopeHandle { clear_element(&element); - self.mount_in_place(element, None, None, None); + self.mount_in_place(element, None, None, None) } // TODO Consider to use &Node instead of Element as parent /// Mounts elements in place of previous node. - pub fn mount_in_place(mut self, element: Element, obsolete: Option>, mut occupied: Option, init_props: Option) { + pub fn mount_in_place(mut self, element: Element, obsolete: Option>, mut occupied: Option, init_props: Option) -> ScopeHandle { let mut component = { let props = init_props.unwrap_or_default(); let mut env = self.get_env(); @@ -358,12 +358,35 @@ where }; // Initial call for first rendering callback(); + let handle = ScopeHandle { + bind: bind.clone(), + }; js! { @(no_return) var bind = @{bind}; var callback = @{callback}; bind.loop = callback; } - // TODO `Drop` should drop the callback + handle + } +} + +/// This handle keeps the reference to a detached scope to prevent memory leaks. +pub struct ScopeHandle { + bind: Value, +} + +impl ScopeHandle { + /// Destroy the scope (component's loop). + pub fn destroy(self) { + let bind = &self.bind; + js! { @(no_return) + var destroy = function() { + var bind = @{bind}; + bind.loop.drop(); + bind.loop = function() { }; + }; + setTimeout(destroy, 0); + } } } diff --git a/src/services/interval.rs b/src/services/interval.rs index f60afef28..3e08c1153 100644 --- a/src/services/interval.rs +++ b/src/services/interval.rs @@ -43,6 +43,14 @@ impl IntervalService { } } +impl Drop for IntervalHandle { + fn drop(&mut self) { + if self.0.is_some() { + self.cancel(); + } + } +} + impl Task for IntervalHandle { fn cancel(&mut self) { let handle = self.0.take().expect("tried to cancel interval twice"); diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 6632a9fbc..6c877cfc3 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -5,13 +5,14 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::any::TypeId; use stdweb::web::{INode, Node, Element, document}; -use html::{ScopeBuilder, SharedContext, Component, Renderable, ComponentUpdate, ScopeSender, Callback, ScopeEnv, NodeCell}; +use html::{ScopeBuilder, SharedContext, Component, Renderable, ComponentUpdate, ScopeSender, Callback, ScopeEnv, NodeCell, ScopeHandle}; use stdweb::unstable::TryInto; use super::{Reform, VDiff, VNode}; struct Hidden; type AnyProps = (TypeId, *mut Hidden); +type HandleCell = Rc>>; /// A virtual component. pub struct VComp> { @@ -19,8 +20,9 @@ pub struct VComp> { cell: NodeCell, props: Option<(TypeId, *mut Hidden)>, blind_sender: Box, - generator: Box, Element, Option, AnyProps)>, + generator: Box, Element, Option, AnyProps) -> ScopeHandle>, activators: Vec>>>>, + handle: Option, _parent: PhantomData, } @@ -47,7 +49,8 @@ impl> VComp { let builder = builder.take().expect("tried to mount component twice"); let opposite = obsolete.map(VNode::VRef); - builder.build(context).mount_in_place(element, opposite, Some(occupied.clone()), Some(props)); + builder.build(context) + .mount_in_place(element, opposite, Some(occupied.clone()), Some(props)) }; let mut previous_props = None; let blind_sender = move |(type_id, raw): AnyProps| { @@ -74,6 +77,7 @@ impl> VComp { blind_sender: Box::new(blind_sender), generator: Box::new(generator), activators: Vec::new(), + handle: None, _parent: PhantomData, }; (properties, comp) @@ -98,7 +102,7 @@ impl> VComp { } /// This methods gives sender from older node. - pub(crate) fn grab_sender_of(&mut self, other: Self) { + pub(crate) fn grab_sender_of(&mut self, mut other: Self) { assert_eq!(self.type_id, other.type_id); // Grab a sender and a cell (element's reference) to reuse it later self.blind_sender = other.blind_sender; @@ -176,7 +180,8 @@ where .to_owned() .try_into() .expect("element expected to mount VComp"); - (self.generator)(context, element, opposite, props); + let handle = (self.generator)(context, element, opposite, props); + self.handle = Some(handle); } fn send_props(&mut self, props: AnyProps) { @@ -196,6 +201,11 @@ where /// Remove VComp from parent. fn remove(self, parent: &Node) -> Option { + // Destroy the loop. It's impossible to use `Drop`, + // because parts can be reused with `grab_sender_of`. + if let Some(handle) = self.handle { + handle.destroy(); + } // Keep the sibling in the cell and send a message `Drop` to a loop self.cell.borrow_mut().take().and_then(|node| { let sibling = node.next_sibling(); From ee0c1280d7ceed30f8cc5d09eeece98e95c8aa2a Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Fri, 9 Feb 2018 23:29:03 +0300 Subject: [PATCH 2/7] Replace service handles with tasks --- src/services/fetch.rs | 19 +++++++++++++++---- src/services/interval.rs | 25 ++++++++++++++----------- src/services/mod.rs | 7 +++++-- src/services/timeout.rs | 19 +++++++++++++++---- src/services/websocket.rs | 23 +++++++++++++++++------ src/virtual_dom/vcomp.rs | 2 +- 6 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/services/fetch.rs b/src/services/fetch.rs index 666809221..67e3ec02d 100644 --- a/src/services/fetch.rs +++ b/src/services/fetch.rs @@ -26,7 +26,7 @@ enum FetchError { } /// A handle to control sent requests. Can be canceled with a `Task::cancel` call. -pub struct FetchHandle(Option); +pub struct FetchTask(Option); /// A service to fetch resources. @@ -90,7 +90,7 @@ impl FetchService { - pub fn fetch(&mut self, request: Request, callback: Callback>) -> FetchHandle + pub fn fetch(&mut self, request: Request, callback: Callback>) -> FetchTask where IN: Into, OUT: From, @@ -170,11 +170,14 @@ impl FetchService { }); return handle; }; - FetchHandle(Some(handle)) + FetchTask(Some(handle)) } } -impl Task for FetchHandle { +impl Task for FetchTask { + fn is_active(&self) -> bool { + self.0.is_some() + } fn cancel(&mut self) { // Fetch API doesn't support request cancelling // and we should use this workaround with a flag. @@ -187,3 +190,11 @@ impl Task for FetchHandle { } } } + +impl Drop for FetchTask { + fn drop(&mut self) { + if self.is_active() { + self.cancel(); + } + } +} diff --git a/src/services/interval.rs b/src/services/interval.rs index 3e08c1153..54d7cba5b 100644 --- a/src/services/interval.rs +++ b/src/services/interval.rs @@ -8,7 +8,7 @@ use super::{Task, to_ms}; /// A handle which helps to cancel interval. Uses /// [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval). -pub struct IntervalHandle(Option); +pub struct IntervalTask(Option); /// A service to send messages on every elapsed interval. pub struct IntervalService { @@ -22,7 +22,7 @@ impl IntervalService { /// Sets interval which will call send a messages returned by a converter /// on every intarval expiration. - pub fn spawn(&mut self, duration: Duration, callback: Callback<()>) -> IntervalHandle + pub fn spawn(&mut self, duration: Duration, callback: Callback<()>) -> IntervalTask { let callback = move || { callback.emit(()); @@ -39,19 +39,14 @@ impl IntervalService { callback, }; }; - IntervalHandle(Some(handle)) + IntervalTask(Some(handle)) } } -impl Drop for IntervalHandle { - fn drop(&mut self) { - if self.0.is_some() { - self.cancel(); - } +impl Task for IntervalTask { + fn is_active(&self) -> bool { + self.0.is_some() } -} - -impl Task for IntervalHandle { fn cancel(&mut self) { let handle = self.0.take().expect("tried to cancel interval twice"); js! { @(no_return) @@ -61,3 +56,11 @@ impl Task for IntervalHandle { } } } + +impl Drop for IntervalTask { + fn drop(&mut self) { + if self.is_active() { + self.cancel(); + } + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 3a4e3a87b..3d5aa7887 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -13,8 +13,11 @@ pub mod websocket; use std::time::Duration; -/// An universal interface to service's routine. At least could be canceled. -pub trait Task { +/// An universal task of a service. +/// It have to be canceled when dropped. +pub trait Task: Drop { + /// Returns `true` if task is active. + fn is_active(&self) -> bool; /// Cancel current service's routine. fn cancel(&mut self); } diff --git a/src/services/timeout.rs b/src/services/timeout.rs index d1a4f517e..c2f5f6150 100644 --- a/src/services/timeout.rs +++ b/src/services/timeout.rs @@ -7,7 +7,7 @@ use html::Callback; use super::{Task, to_ms}; /// A handle to cancel a timeout task. -pub struct TimeoutHandle(Option); +pub struct TimeoutTask(Option); /// An service to set a timeout. pub struct TimeoutService { @@ -20,7 +20,7 @@ impl TimeoutService { } /// Sets timeout which send a messages from a `converter` after `duration`. - pub fn spawn(&mut self, duration: Duration, callback: Callback<()>) -> TimeoutHandle { + pub fn spawn(&mut self, duration: Duration, callback: Callback<()>) -> TimeoutTask { let callback = move || { callback.emit(()); }; @@ -37,11 +37,14 @@ impl TimeoutService { callback, }; }; - TimeoutHandle(Some(handle)) + TimeoutTask(Some(handle)) } } -impl Task for TimeoutHandle { +impl Task for TimeoutTask { + fn is_active(&self) -> bool { + self.0.is_some() + } fn cancel(&mut self) { let handle = self.0.take().expect("tried to cancel timeout twice"); js! { @(no_return) @@ -51,3 +54,11 @@ impl Task for TimeoutHandle { } } } + +impl Drop for TimeoutTask { + fn drop(&mut self) { + if self.is_active() { + self.cancel(); + } + } +} diff --git a/src/services/websocket.rs b/src/services/websocket.rs index e02a49620..cba614859 100644 --- a/src/services/websocket.rs +++ b/src/services/websocket.rs @@ -15,7 +15,7 @@ pub enum WebSocketStatus { } /// A handle to control current websocket connection. Implements `Task` and could be canceled. -pub struct WebSocketHandle(Option); +pub struct WebSocketTask(Option); /// A websocket service attached to a user context. pub struct WebSocketService { @@ -29,7 +29,7 @@ impl WebSocketService { /// Connects to a server by a weboscket connection. Needs two functions to generate /// data and notification messages. - pub fn connect(&mut self, url: &str, callback: Callback, notification: Callback) -> WebSocketHandle + pub fn connect(&mut self, url: &str, callback: Callback, notification: Callback) -> WebSocketTask where OUT: From, { @@ -69,17 +69,17 @@ impl WebSocketService { socket, }; }; - WebSocketHandle(Some(handle)) + WebSocketTask(Some(handle)) } } -impl WebSocketHandle { +impl WebSocketTask { /// Sends data to a websocket connection. pub fn send(&mut self, data: IN) where IN: Into { - if let WebSocketHandle(Some(ref handle)) = *self { + if let WebSocketTask(Some(ref handle)) = *self { if let Some(body) = data.into() { js! { @(no_return) var handle = @{handle}; @@ -92,7 +92,10 @@ impl WebSocketHandle { } } -impl Task for WebSocketHandle { +impl Task for WebSocketTask { + fn is_active(&self) -> bool { + self.0.is_some() + } fn cancel(&mut self) { let handle = self.0.take().expect("tried to close websocket twice"); js! { @(no_return) @@ -101,3 +104,11 @@ impl Task for WebSocketHandle { } } } + +impl Drop for WebSocketTask { + fn drop(&mut self) { + if self.is_active() { + self.cancel(); + } + } +} diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 6c877cfc3..e13e0ab23 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -102,7 +102,7 @@ impl> VComp { } /// This methods gives sender from older node. - pub(crate) fn grab_sender_of(&mut self, mut other: Self) { + pub(crate) fn grab_sender_of(&mut self, other: Self) { assert_eq!(self.type_id, other.type_id); // Grab a sender and a cell (element's reference) to reuse it later self.blind_sender = other.blind_sender; From fda86284b4dc776b9c5e5e20fb6a7ac6c2e404d3 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Fri, 9 Feb 2018 23:50:25 +0300 Subject: [PATCH 3/7] Move ScopeHandle to a builder --- src/html.rs | 17 ++++++++++------- src/virtual_dom/vcomp.rs | 4 +++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/html.rs b/src/html.rs index bcb1b0c9f..a797b21f8 100644 --- a/src/html.rs +++ b/src/html.rs @@ -237,6 +237,13 @@ impl> ScopeBuilder { } } + /// Return handler to a scope. Warning! Don't use more than one handle! + pub fn handle(&self) -> ScopeHandle { + ScopeHandle { + bind: self.bind.clone(), + } + } + pub fn build(self, context: SharedContext) -> Scope { Scope { tx: self.tx, @@ -294,7 +301,7 @@ where COMP: Component + Renderable, { /// Alias to `mount("body", ...)`. - pub fn mount_to_body(self) -> ScopeHandle { + pub fn mount_to_body(self) { let element = document().query_selector("body") .expect("can't get body node for rendering"); self.mount(element) @@ -304,14 +311,14 @@ where /// function in Elm. You should provide an initial model, `update` function /// which will update the state of the model and a `view` function which /// will render the model to a virtual DOM tree. - pub fn mount(self, element: Element) -> ScopeHandle { + pub fn mount(self, element: Element) { clear_element(&element); self.mount_in_place(element, None, None, None) } // TODO Consider to use &Node instead of Element as parent /// Mounts elements in place of previous node. - pub fn mount_in_place(mut self, element: Element, obsolete: Option>, mut occupied: Option, init_props: Option) -> ScopeHandle { + pub fn mount_in_place(mut self, element: Element, obsolete: Option>, mut occupied: Option, init_props: Option) { let mut component = { let props = init_props.unwrap_or_default(); let mut env = self.get_env(); @@ -358,15 +365,11 @@ where }; // Initial call for first rendering callback(); - let handle = ScopeHandle { - bind: bind.clone(), - }; js! { @(no_return) var bind = @{bind}; var callback = @{callback}; bind.loop = callback; } - handle } } diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index e13e0ab23..bccacd738 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -49,8 +49,10 @@ impl> VComp { let builder = builder.take().expect("tried to mount component twice"); let opposite = obsolete.map(VNode::VRef); + let handle = builder.handle(); builder.build(context) - .mount_in_place(element, opposite, Some(occupied.clone()), Some(props)) + .mount_in_place(element, opposite, Some(occupied.clone()), Some(props)); + handle }; let mut previous_props = None; let blind_sender = move |(type_id, raw): AnyProps| { From 58729bbc1d5010c2b2c6ba7a9662e04d39bde230 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 10 Feb 2018 09:51:18 +0300 Subject: [PATCH 4/7] Keep scope handle when component updated --- src/html.rs | 25 ++++++++++++++----------- src/virtual_dom/vcomp.rs | 11 +++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/html.rs b/src/html.rs index a797b21f8..0c87ace54 100644 --- a/src/html.rs +++ b/src/html.rs @@ -198,17 +198,20 @@ impl> Clone for ScopeSender { impl> ScopeSender { /// Send the message and schedule an update. pub fn send(&mut self, update: ComponentUpdate) { - self.tx.send(update).expect("app lost the receiver!"); - let bind = &self.bind; - js! { @(no_return) - // Schedule to call the loop handler - // IMPORTANT! If call loop function immediately - // it stops handling other messages and the first - // one will be fired. - var bind = @{bind}; - // Put bind holder instad of callback function, because - // scope could be dropped and `loop` function will be changed - window._yew_schedule_(bind); + if let Ok(()) = self.tx.send(update) { + let bind = &self.bind; + js! { @(no_return) + // Schedule to call the loop handler + // IMPORTANT! If call loop function immediately + // it stops handling other messages and the first + // one will be fired. + var bind = @{bind}; + // Put bind holder instad of callback function, because + // scope could be dropped and `loop` function will be changed + window._yew_schedule_(bind); + } + } else { + eprintln!("app lost the receiver!"); } } } diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index bccacd738..5a5a07a39 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -20,7 +20,7 @@ pub struct VComp> { cell: NodeCell, props: Option<(TypeId, *mut Hidden)>, blind_sender: Box, - generator: Box, Element, Option, AnyProps) -> ScopeHandle>, + generator: Box, Element, Option, AnyProps)>, activators: Vec>>>>, handle: Option, _parent: PhantomData, @@ -34,6 +34,7 @@ impl> VComp { { let cell: NodeCell = Rc::new(RefCell::new(None)); let builder: ScopeBuilder = ScopeBuilder::new(); + let handle = Some(builder.handle()); let mut sender = builder.sender(); let mut builder = Some(builder); let occupied = cell.clone(); @@ -49,10 +50,8 @@ impl> VComp { let builder = builder.take().expect("tried to mount component twice"); let opposite = obsolete.map(VNode::VRef); - let handle = builder.handle(); builder.build(context) .mount_in_place(element, opposite, Some(occupied.clone()), Some(props)); - handle }; let mut previous_props = None; let blind_sender = move |(type_id, raw): AnyProps| { @@ -79,7 +78,7 @@ impl> VComp { blind_sender: Box::new(blind_sender), generator: Box::new(generator), activators: Vec::new(), - handle: None, + handle, _parent: PhantomData, }; (properties, comp) @@ -109,6 +108,7 @@ impl> VComp { // Grab a sender and a cell (element's reference) to reuse it later self.blind_sender = other.blind_sender; self.cell = other.cell; + self.handle = other.handle; } } @@ -182,8 +182,7 @@ where .to_owned() .try_into() .expect("element expected to mount VComp"); - let handle = (self.generator)(context, element, opposite, props); - self.handle = Some(handle); + (self.generator)(context, element, opposite, props); } fn send_props(&mut self, props: AnyProps) { From f2029fa92d42deac7c8b3ab6e5608946ae9638fa Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 10 Feb 2018 15:47:58 +0300 Subject: [PATCH 5/7] Improve error message when a sender outlives the receiver --- src/html.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/html.rs b/src/html.rs index 0c87ace54..e03cfb402 100644 --- a/src/html.rs +++ b/src/html.rs @@ -211,7 +211,8 @@ impl> ScopeSender { window._yew_schedule_(bind); } } else { - eprintln!("app lost the receiver!"); + eprintln!("Can't send message to a component. Receiver lost! \ + Maybe Task lives longer than a component instance."); } } } From 5f056c5c6854c2fe88d50ffa0c7fe26efd8d5fb0 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 10 Feb 2018 18:44:21 +0300 Subject: [PATCH 6/7] Fix tests: add properties to component creation --- tests/vlist_test.rs | 2 +- tests/vtag_test.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/vlist_test.rs b/tests/vlist_test.rs index 015cc0262..2e37ce5ef 100644 --- a/tests/vlist_test.rs +++ b/tests/vlist_test.rs @@ -13,7 +13,7 @@ impl Component for Comp { type Msg = (); type Properties = (); - fn create(_: &mut Env) -> Self { + fn create(_: Self::Properties, _: &mut Env) -> Self { Comp } diff --git a/tests/vtag_test.rs b/tests/vtag_test.rs index 4fc1c20c2..ae01427f4 100644 --- a/tests/vtag_test.rs +++ b/tests/vtag_test.rs @@ -12,7 +12,7 @@ impl Component for Comp { type Msg = (); type Properties = (); - fn create(_: &mut Env) -> Self { + fn create(_: Self::Properties, _: &mut Env) -> Self { Comp } From 99d8131ff7afc3d4743e6251992143866e016f66 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 10 Feb 2018 18:45:05 +0300 Subject: [PATCH 7/7] Fix examples: use tasks explicitly --- examples/dashboard/client/src/main.rs | 15 +++++++++------ examples/npm_and_rest/src/gravatar.rs | 4 ++-- examples/npm_and_rest/src/main.rs | 6 +++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/dashboard/client/src/main.rs b/examples/dashboard/client/src/main.rs index bab7de08b..6a1d204c0 100644 --- a/examples/dashboard/client/src/main.rs +++ b/examples/dashboard/client/src/main.rs @@ -6,8 +6,8 @@ extern crate yew; use yew::prelude::*; use yew::format::{Nothing, Json}; use yew::services::Task; -use yew::services::fetch::{FetchService, Request, Response}; -use yew::services::websocket::{WebSocketService, WebSocketHandle, WebSocketStatus}; +use yew::services::fetch::{FetchService, FetchTask, Request, Response}; +use yew::services::websocket::{WebSocketService, WebSocketTask, WebSocketStatus}; struct Context { web: FetchService, @@ -17,7 +17,8 @@ struct Context { struct Model { fetching: bool, data: Option, - ws: Option, + ft: Option, + ws: Option, } enum WsAction { @@ -68,6 +69,7 @@ impl Component for Model { Model { fetching: false, data: None, + ft: None, ws: None, } } @@ -85,7 +87,8 @@ impl Component for Model { } }); let request = Request::get("/data.json").body(Nothing).unwrap(); - context.web.fetch(request, callback); + let task = context.web.fetch(request, callback); + self.ft = Some(task); } Msg::WsAction(action) => { match action { @@ -97,8 +100,8 @@ impl Component for Model { WebSocketStatus::Closed => WsAction::Lost.into(), } }); - let handle = context.ws.connect("ws://localhost:9001/", callback, notification); - self.ws = Some(handle); + let task = context.ws.connect("ws://localhost:9001/", callback, notification); + self.ws = Some(task); } WsAction::SendData => { let request = WsRequest { diff --git a/examples/npm_and_rest/src/gravatar.rs b/examples/npm_and_rest/src/gravatar.rs index 89bed62ef..c58e54519 100644 --- a/examples/npm_and_rest/src/gravatar.rs +++ b/examples/npm_and_rest/src/gravatar.rs @@ -1,5 +1,5 @@ use yew::format::{Nothing, Json}; -use yew::services::fetch::{FetchService, FetchHandle, Request, Response}; +use yew::services::fetch::{FetchService, FetchTask, Request, Response}; use yew::html::Callback; #[derive(Deserialize, Debug)] @@ -28,7 +28,7 @@ impl GravatarService { } } - pub fn profile(&mut self, hash: &str, callback: Callback>) -> FetchHandle { + pub fn profile(&mut self, hash: &str, callback: Callback>) -> FetchTask { let url = format!("https://gravatar.com/{}", hash); let handler = move |response: Response>>| { let (_, Json(data)) = response.into_parts(); diff --git a/examples/npm_and_rest/src/main.rs b/examples/npm_and_rest/src/main.rs index 5b96a73ba..0fa0b36b5 100644 --- a/examples/npm_and_rest/src/main.rs +++ b/examples/npm_and_rest/src/main.rs @@ -6,6 +6,7 @@ extern crate stdweb; extern crate yew; use yew::prelude::*; +use yew::services::fetch::FetchTask; // Own services implementation mod gravatar; @@ -21,6 +22,7 @@ struct Context { struct Model { profile: Option, exchanges: Vec, + task: Option, } enum Msg { @@ -37,6 +39,7 @@ impl Component for Model { Model { profile: None, exchanges: Vec::new(), + task: None, } } @@ -44,7 +47,8 @@ impl Component for Model { match msg { Msg::Gravatar => { let callback = context.send_back(Msg::GravatarReady); - context.gravatar.profile("205e460b479e2e5b48aec07710c08d50", callback); + let task = context.gravatar.profile("205e460b479e2e5b48aec07710c08d50", callback); + self.task = Some(task); } Msg::GravatarReady(Ok(profile)) => { self.profile = Some(profile);