Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add send_future to ComponentLink #717

Merged
merged 16 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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