Skip to content

Commit

Permalink
Add send_future to ComponentLink (#717)
Browse files Browse the repository at this point in the history
* bump wasm-bindgen to 0.2.51 (#681)

* upgrade wasm-bindgen to 0.2.51

* Run macro tests on beta and update readme

* Update README.md

* Update .travis.yml

* implement send_future

* cargo fmt

* add todo question

* static dispatch on Error in future

* Add framework for example using futures

* add a basic future to indicate that futures work

* change example to fetch markdown instead of just resolving a string

* remove unnneded import

* Force users to handle errors in their future

* Force users to handle errors in their future

* cleanup and introduction of FetchState

* respond to feedback

* update comment

* fix cargo.toml dependency issues
  • Loading branch information
hgzimmerman authored and jstarry committed Nov 11, 2019
1 parent 6ba83fc commit f60e533
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 3 deletions.
7 changes: 4 additions & 3 deletions Cargo.toml
Expand Up @@ -39,6 +39,10 @@ yew-macro = { version = "0.10.0", path = "crates/macro" }

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.54"
wasm-bindgen-futures = "0.4.4"

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dev-dependencies]
wasm-bindgen-test = "0.3.4"

[target.'cfg(target_os = "emscripten")'.dependencies]
ryu = "=1.0.0" # 1.0.1 breaks emscripten
Expand All @@ -48,9 +52,6 @@ serde_derive = "1"
trybuild = "1.0"
rustversion = "0.1"

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dev-dependencies]
wasm-bindgen-test = "0.3.4"

[features]
default = []
doc_test = []
Expand Down
1 change: 1 addition & 0 deletions examples/Cargo.toml
Expand Up @@ -7,6 +7,7 @@ members = [
"node_refs",
"file_upload",
"fragments",
"futures",
"game_of_life",
"inner_html",
"js_callback",
Expand Down
29 changes: 29 additions & 0 deletions examples/futures/Cargo.toml
@@ -0,0 +1,29 @@
[package]
name = "futures"
version = "0.1.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
description = "web_sys fetching and futures demonstration"
license = "MIT/Apache"
repository = "https://github.com/yewstack/yew"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
yew = { path = "../.." }
wasm-bindgen-futures = "0.4.3"

[dependencies.web-sys]
version = "0.3.30"
features = [
'Headers',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Window',
]

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.51"
14 changes: 14 additions & 0 deletions examples/futures/README.md
@@ -0,0 +1,14 @@
# Futures Example
This example shows off how to make a asynchronous fetch request using web_sys and Yew's futures support.

Because this example uses features not allowed by cargo web, it cannot be included in the showcase, and must be built with a different toolchain instead.

### How to run:
This example requires rustc v1.39.0 or above to compile due to its use of async/.await syntax.

```sh
wasm-pack build --target web && rollup ./main.js --format iife --file ./pkg/bundle.js && python -m SimpleHTTPServer 8080
```
This will compile the project, bundle up the compiler output and static assets, and start a http server on port 8080 so you can access the example at localhost:8080.

It is expected that you have a setup with wasm-pack, rollup, and python installed.
10 changes: 10 additions & 0 deletions examples/futures/index.html
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Futures</title>
</head>
<body>
<script src="/pkg/bundle.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions examples/futures/main.js
@@ -0,0 +1,6 @@
import init, { run_app } from './pkg/futures.js';
async function main() {
await init('./pkg/futures_bg.wasm');
run_app();
}
main()
118 changes: 118 additions & 0 deletions examples/futures/src/lib.rs
@@ -0,0 +1,118 @@
use crate::Msg::SetMarkdownFetchState;
use std::fmt::{Error, Formatter};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response, Window};
use yew::{html, Component, ComponentLink, Html, ShouldRender};

/// Something wrong has occurred while fetching an external resource.
#[derive(Debug, Clone, PartialEq)]
pub struct FetchError {
err: JsValue,
}
impl std::fmt::Display for FetchError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
std::fmt::Debug::fmt(&self.err, f)
}
}
impl std::error::Error for FetchError {}

impl From<JsValue> for FetchError {
fn from(value: JsValue) -> Self {
FetchError { err: value }
}
}

/// The possible states a fetch request can be in.
pub enum FetchState<T> {
NotFetching,
Fetching,
Success(T),
Failed(FetchError),
}

/// Fetches markdown from Yew's README.md.
///
/// Consult the following for an example of the fetch api by the team behind web_sys:
/// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
async fn fetch_markdown() -> Result<String, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);

let request = Request::new_with_str_and_init(
"https://raw.githubusercontent.com/yewstack/yew/master/README.md",
&opts,
)?;

let window: Window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
assert!(resp_value.is_instance_of::<Response>());

let resp: Response = resp_value.dyn_into().unwrap();

let text = JsFuture::from(resp.text()?).await?;
Ok(text.as_string().unwrap())
}

struct Model {
markdown: FetchState<String>,
link: ComponentLink<Self>,
}

enum Msg {
SetMarkdownFetchState(FetchState<String>),
GetMarkdown,
}

impl Component for Model {
// Some details omitted. Explore the examples to see more.

type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Model {
markdown: FetchState::NotFetching,
link,
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SetMarkdownFetchState(fetch_state) => {
self.markdown = fetch_state;
true
}
Msg::GetMarkdown => {
let future = async {
match fetch_markdown().await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
};
self.link.send_future(future);
self.link
.send_self(SetMarkdownFetchState(FetchState::Fetching));
false
}
}
}

fn view(&self) -> Html<Self> {
match &self.markdown {
FetchState::NotFetching => {
html! {<button onclick=|_| Msg::GetMarkdown>{"Get Markdown"}</button>}
}
FetchState::Fetching => html! {"Fetching"},
FetchState::Success(data) => html! {&data},
FetchState::Failed(err) => html! {&err},
}
}
}

#[wasm_bindgen]
pub fn run_app() {
yew::start_app::<Model>();
}
28 changes: 28 additions & 0 deletions src/html/mod.rs
Expand Up @@ -19,6 +19,9 @@ use std::rc::Rc;
use stdweb::unstable::TryFrom;
use stdweb::web::Node;

#[cfg(all(target_arch = "wasm32", not(cargo_web)))]
use std::future::Future;

/// This type indicates that component should be rendered again.
pub type ShouldRender = bool;

Expand Down Expand Up @@ -407,6 +410,31 @@ where
closure.into()
}

#[cfg(all(target_arch = "wasm32", not(cargo_web)))]
/// This method processes a Future that returns a message and sends it back to the component's
/// loop.
///
/// # Panics
/// If the future panics, then the promise will not resolve, and will leak.
pub fn send_future<F>(&self, future: F)
where
F: Future<Output = COMP::Message> + 'static,
{
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;

let mut scope = self.scope.clone();

let js_future = async {
let message: COMP::Message = future.await;
// Force movement of the cloned scope into the async block.
let scope_send = move || scope.send_message(message);
scope_send();
Ok(JsValue::NULL)
};
future_to_promise(js_future);
}

/// This method sends a message to this component immediately.
pub fn send_self(&mut self, msg: COMP::Message) {
self.scope.send_message(msg);
Expand Down

0 comments on commit f60e533

Please sign in to comment.