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 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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