mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-14 21:41:23 +00:00
Add support for #[wasm_bindgen]
on async fn
(#1754)
This commit adds support to attach `#[wasm_bindgen]` on an `async fn` which will change the return value into a `Promise` in JS. This in theory has the exact same semantics as an `async` function in JS where you call it with all the arguments, nothing happens and you get a promise back, and then later the promise actually resolves. This commit also adds a helper trait, `IntoJsResult`, to allow `async` functions with multiple kinds of return values instead of requiring everything to be `Result<JsValue, JsValue>`.
This commit is contained in:
@ -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))]
|
||||
|
@ -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();
|
||||
};
|
||||
@ -457,7 +474,7 @@ 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! {}
|
||||
@ -465,11 +482,13 @@ impl TryToTokens for ast::Export {
|
||||
|
||||
(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
|
||||
|
@ -713,6 +713,7 @@ fn function_from_decl(
|
||||
ret,
|
||||
rust_attrs: attrs,
|
||||
rust_vis: vis,
|
||||
r#async: sig.asyncness.is_some(),
|
||||
},
|
||||
method_self,
|
||||
))
|
||||
|
@ -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
crates/macro/ui-tests/async-errors.rs
Normal file
39
crates/macro/ui-tests/async-errors.rs
Normal file
@ -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
crates/macro/ui-tests/async-errors.stderr
Normal file
50
crates/macro/ui-tests/async-errors.stderr
Normal file
@ -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`.
|
@ -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(),
|
||||
|
@ -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};
|
||||
|
||||
@ -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);
|
||||
|
@ -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
|
||||
|
40
src/lib.rs
40
src/lib.rs
@ -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;
|
||||
@ -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`
|
||||
|
Reference in New Issue
Block a user