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 support for #[wasm_bindgen] on async fn #1754

Merged
merged 1 commit into from
Sep 6, 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
1 change: 1 addition & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ pub struct Function {
pub ret: Option<syn::Type>,
pub rust_attrs: Vec<syn::Attribute>,
pub rust_vis: syn::Visibility,
pub r#async: bool,
}

#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
Expand Down
39 changes: 29 additions & 10 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,13 +442,30 @@ impl TryToTokens for ast::Export {
if let syn::Type::Reference(_) = syn_ret {
bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",)
}
let ret_ty = quote! {
-> <#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>::Abi
};
let convert_ret = quote! {
<#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>
::return_abi(#ret)

// For an `async` function we always run it through `future_to_promise`
// since we're returning a promise to JS, and this will implicitly
// require that the function returns a `Future<Output = Result<...>>`
let (ret_expr, projection) = if self.function.r#async {
(
quote! {
wasm_bindgen_futures::future_to_promise(async {
wasm_bindgen::__rt::IntoJsResult::into_js_result(#ret.await)
}).into()
},
quote! {
<wasm_bindgen::JsValue as wasm_bindgen::convert::ReturnWasmAbi>
},
)
} else {
(
quote! { #ret },
quote! {
<#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>
},
)
};
let convert_ret = quote! { #projection::return_abi(#ret_expr) };
let describe_ret = quote! {
<#syn_ret as WasmDescribe>::describe();
};
Expand All @@ -457,19 +474,21 @@ impl TryToTokens for ast::Export {

let start_check = if self.start {
quote! {
const _ASSERT: fn() = || #ret_ty { loop {} };
const _ASSERT: fn() = || -> #projection::Abi { loop {} };
}
} else {
quote! {}
};

(quote! {
#(#attrs)*
#[export_name = #export_name]
#[allow(non_snake_case)]
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "emscripten")),
export_name = #export_name,
)]
#[allow(clippy::all)]
pub extern "C" fn #generated_name(#(#args),*) #ret_ty {
pub extern "C" fn #generated_name(#(#args),*) -> #projection::Abi {
#start_check
// Scope all local variables to be destroyed after we call the
// function to ensure that `#convert_ret`, if it panics, doesn't
Expand Down
1 change: 1 addition & 0 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ fn function_from_decl(
ret,
rust_attrs: attrs,
rust_vis: vis,
r#async: sig.asyncness.is_some(),
},
method_self,
))
Expand Down
1 change: 1 addition & 0 deletions crates/macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ quote = "1.0"
[dev-dependencies]
trybuild = "1.0"
wasm-bindgen = { path = "../..", version = "0.2.50", features = ['strict-macro'] }
wasm-bindgen-futures = { path = "../futures", version = "0.4.0" }
39 changes: 39 additions & 0 deletions crates/macro/ui-tests/async-errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct MyType;

#[wasm_bindgen]
pub async fn good1() { loop {} }
#[wasm_bindgen]
pub async fn good2() -> JsValue { loop {} }
#[wasm_bindgen]
pub async fn good3() -> u32 { loop {} }
#[wasm_bindgen]
pub async fn good4() -> MyType { loop {} }
#[wasm_bindgen]
pub async fn good5() -> Result<(), JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good6() -> Result<JsValue, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good7() -> Result<u32, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good8() -> Result<MyType, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good9() -> Result<MyType, u32> { loop {} }
#[wasm_bindgen]
pub async fn good10() -> Result<MyType, MyType> { loop {} }

pub struct BadType;

#[wasm_bindgen]
pub async fn bad1() -> Result<(), ()> { loop {} }
#[wasm_bindgen]
pub async fn bad2() -> Result<(), BadType> { loop {} }
#[wasm_bindgen]
pub async fn bad3() -> BadType { loop {} }
#[wasm_bindgen]
pub async fn bad4() -> Result<BadType, JsValue> { loop {} }


fn main() {}
50 changes: 50 additions & 0 deletions crates/macro/ui-tests/async-errors.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
error[E0277]: the trait bound `std::result::Result<(), ()>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:29:1
|
29 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<(), ()>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `std::result::Result<(), BadType>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:31:1
|
31 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<(), BadType>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `wasm_bindgen::JsValue: std::convert::From<BadType>` is not satisfied
--> $DIR/async-errors.rs:33:1
|
33 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `std::convert::From<BadType>` is not implemented for `wasm_bindgen::JsValue`
|
= help: the following implementations were found:
<wasm_bindgen::JsValue as std::convert::From<&'a T>>
<wasm_bindgen::JsValue as std::convert::From<&'a std::string::String>>
<wasm_bindgen::JsValue as std::convert::From<&'a str>>
<wasm_bindgen::JsValue as std::convert::From<MyType>>
and 62 others
= note: required because of the requirements on the impl of `std::convert::Into<wasm_bindgen::JsValue>` for `BadType`
= note: required because of the requirements on the impl of `wasm_bindgen::__rt::IntoJsResult` for `BadType`
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `std::result::Result<BadType, wasm_bindgen::JsValue>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:35:1
|
35 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<BadType, wasm_bindgen::JsValue>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

For more information about this error, try `rustc --explain E0277`.
1 change: 1 addition & 0 deletions crates/webidl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ impl<'src> FirstPassRecord<'src> {
ret: ret.clone(),
rust_attrs: vec![],
rust_vis: public(),
r#async: false,
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
Expand Down
8 changes: 1 addition & 7 deletions examples/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use js_sys::Promise;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};

Expand Down Expand Up @@ -35,11 +33,7 @@ pub struct Signature {
}

#[wasm_bindgen]
pub fn run() -> Promise {
future_to_promise(run_())
}

async fn run_() -> Result<JsValue, JsValue> {
pub async fn run() -> Result<JsValue, JsValue> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
Expand Down
74 changes: 73 additions & 1 deletion guide/src/reference/js-promises-and-rust-futures.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
# Converting Between JavaScript `Promise`s and Rust `Future`s
# Working with a JS `Promise` and a Rust `Future`

Many APIs on the web work with a `Promise`, such as an `async` function in JS.
Naturally you'll probably want to interoperate with them from Rust! To do that
you can use the `wasm-bindgen-futures` crate as well as Rust `async`
functions.

The first thing you might encounter is the need for working with a `Promise`.
For this you'll want to use [`js_sys::Promise`]. Once you've got one of those
values you can convert that value to `wasm_bindgen_futures::JsFuture`. This type
implements the `std::future::Future` trait which allows naturally using it in an
`async` function. For example:

[`js_sys::Promise`]: https://docs.rs/js-sys/*/js_sys/struct.Promise.html

```rust
async fn get_from_js() -> Result<JsValue, JsValue> {
let promise = js_sys::Promise::resolved(&42.into());
let result = wasm_bindgen_futures::JsFuture::from(promise).await?;
Ok(result)
}
```

Here we can see how converting a `Promise` to Rust creates a `impl Future<Output
= Result<JsValue, JsValue>>`. This corresponds to `then` and `catch` in JS where
a successful promise becomes `Ok` and an erroneous promise becomes `Err`.

Next up you'll probably want to export a Rust function to JS that returns a
promise. To do this you can use an `async` function and `#[wasm_bindgen]`:

```rust
#[wasm_bindgen]
pub async fn foo() {
// ...
}
```

When invoked from JS the `foo` function here will return a `Promise`, so you can
import this as:

```js
import { foo } from "my-module";

async function shim() {
const result = await foo();
// ...
}
```

## Return values of `async fn`

When using an `async fn` in Rust and exporting it to JS there's some
restrictions on the return type. The return value of an exported Rust function
will eventually become `Result<JsValue, JsValue>` where `Ok` turns into a
successfully resolved promise and `Err` is equivalent to throwing an exception.

The following types are supported as return types from an `async fn`:

* `()` - turns into a successful `undefined` in JS
* `T: Into<JsValue>` - turns into a successful JS value
* `Result<(), E: Into<JsValue>>` - if `Ok(())` turns into a successful
`undefined` and otherwise turns into a failed promise with `E` converted to a
JS value
* `Result<T: Into<JsValue>, E: Into<JsValue>>` - like the previous case except
both data payloads are converted into a `JsValue`.

Note that many types implement being converted into a `JsValue`, such as all
imported types via `#[wasm_bindgen]` (aka those in `js-sys` or `web-sys`),
primitives like `u32`, and all exported `#[wasm_bindgen]` types. In general,
you should be able to write code without having too many explicit conversions,
and the macro should take care of the rest!

## Using `wasm-bindgen-futures`

The `wasm-bindgen-futures` crate bridges the gap between JavaScript `Promise`s
and Rust `Future`s. Its `JsFuture` type provides conversion from a JavaScript
Expand Down
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,8 @@ pub fn function_table() -> JsValue {
pub mod __rt {
use core::cell::{Cell, UnsafeCell};
use core::ops::{Deref, DerefMut};
use crate::JsValue;

pub extern crate core;
#[cfg(feature = "std")]
pub extern crate std;
Expand Down Expand Up @@ -1095,6 +1097,44 @@ pub mod __rt {
return ret;
}
}

/// An internal helper trait for usage in `#[wasm_bindgen]` on `async`
/// functions to convert the return value of the function to
/// `Result<JsValue, JsValue>` which is what we'll return to JS (where an
/// error is a failed future).
pub trait IntoJsResult {
fn into_js_result(self) -> Result<JsValue, JsValue>;
}

impl IntoJsResult for () {
fn into_js_result(self) -> Result<JsValue, JsValue> {
Ok(JsValue::undefined())
}
}

impl<T: Into<JsValue>> IntoJsResult for T {
fn into_js_result(self) -> Result<JsValue, JsValue> {
Ok(self.into())
}
}

impl<T: Into<JsValue>, E: Into<JsValue>> IntoJsResult for Result<T, E> {
fn into_js_result(self) -> Result<JsValue, JsValue> {
match self {
Ok(e) => Ok(e.into()),
Err(e) => Err(e.into()),
}
}
}

impl<E: Into<JsValue>> IntoJsResult for Result<(), E> {
fn into_js_result(self) -> Result<JsValue, JsValue> {
match self {
Ok(()) => Ok(JsValue::undefined()),
Err(e) => Err(e.into()),
}
}
}
}

/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`
Expand Down