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

Implement extern "C" async functions. #2196

Merged
merged 2 commits into from
Jun 29, 2020
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
61 changes: 50 additions & 11 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,22 +971,54 @@ impl TryToTokens for ast::ImportFunction {
);
}
Some(ref ty) => {
abi_ret = quote! {
<#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
};
convert_ret = quote! {
<#ty as wasm_bindgen::convert::FromWasmAbi>
::from_abi(#ret_ident)
};
if self.function.r#async {
abi_ret =
quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
let future = quote! {
wasm_bindgen_futures::JsFuture::from(
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
::from_abi(#ret_ident)
).await
};
convert_ret = if self.catch {
quote! { Ok(#future?) }
} else {
quote! { #future.expect("unexpected exception") }
};
} else {
abi_ret = quote! {
<#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
};
convert_ret = quote! {
<#ty as wasm_bindgen::convert::FromWasmAbi>
::from_abi(#ret_ident)
};
}
}
None => {
abi_ret = quote! { () };
convert_ret = quote! { () };
if self.function.r#async {
abi_ret =
quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
let future = quote! {
wasm_bindgen_futures::JsFuture::from(
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
::from_abi(#ret_ident)
).await
};
convert_ret = if self.catch {
quote! { #future?; Ok(()) }
} else {
quote! { #future.expect("uncaught exception"); }
};
} else {
abi_ret = quote! { () };
convert_ret = quote! { () };
}
}
}

let mut exceptional_ret = quote!();
if self.catch {
if self.catch && !self.function.r#async {
convert_ret = quote! { Ok(#convert_ret) };
exceptional_ret = quote! {
wasm_bindgen::__rt::take_last_exception()?;
Expand Down Expand Up @@ -1045,12 +1077,17 @@ impl TryToTokens for ast::ImportFunction {
&self.rust_name,
);

let maybe_async = if self.function.r#async {
Some(quote! {async})
} else {
None
};
let invocation = quote! {
#(#attrs)*
#[allow(bad_style)]
#[doc = #doc_comment]
#[allow(clippy::all)]
#vis fn #rust_name(#me #(#arguments),*) #ret {
#vis #maybe_async fn #rust_name(#me #(#arguments),*) #ret {
#extern_fn

unsafe {
Expand Down Expand Up @@ -1096,6 +1133,8 @@ impl<'a> ToTokens for DescribeImport<'a> {
let nargs = f.function.arguments.len() as u32;
let inform_ret = match &f.js_ret {
Some(ref t) => quote! { <#t as WasmDescribe>::describe(); },
// async functions always return a JsValue, even if they say to return ()
None if f.function.r#async => quote! { <JsValue as WasmDescribe>::describe(); },
None => quote! { <() as WasmDescribe>::describe(); },
};

Expand Down
25 changes: 25 additions & 0 deletions guide/src/reference/js-promises-and-rust-futures.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ 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`.

You can also import a JS async function directly with a `extern "C"` block, and
the promise will be converted to a future automatically. For now the return type
must be `JsValue` or no return at all:

```rust
#[wasm_bindgen]
extern "C" {
async fn async_func_1() -> JsValue;
async fn async_func_2();
}
```

The `async` can be combined with the `catch` attribute to manage errors from the
JS promise:

```rust
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(catch)]
async fn async_func_3() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
async fn async_func_4() -> Result<(), JsValue>;
}
```

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]`:

Expand Down
23 changes: 23 additions & 0 deletions tests/wasm/futures.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,26 @@ exports.call_exports = async function() {
assert.strictEqual(8, (await wasm.async_return_8()).val);
await assert.rejects(wasm.async_throw(), /async message/);
};

exports.call_promise = async function() {
return "ok";
}

exports.call_promise_ok = async function() {
return "ok";
}

exports.call_promise_err = async function() {
throw "error";
}

exports.call_promise_unit = async function() {
console.log("asdfasdf");
}

exports.call_promise_ok_unit = async function() {
}

exports.call_promise_err_unit = async function() {
throw "error";
}
60 changes: 56 additions & 4 deletions tests/wasm/futures.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;

#[rustfmt::skip]
#[wasm_bindgen(module = "tests/wasm/futures.js")]
extern "C" {
fn call_exports() -> js_sys::Promise;
#[wasm_bindgen(catch)]
async fn call_exports() -> Result<JsValue, JsValue>;

async fn call_promise() -> JsValue;
#[wasm_bindgen(catch)]
async fn call_promise_ok() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
async fn call_promise_err() -> Result<JsValue, JsValue>;

#[wasm_bindgen]
async fn call_promise_unit();
#[wasm_bindgen(catch)]
async fn call_promise_ok_unit() -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
async fn call_promise_err_unit() -> Result<(), JsValue>;
}

#[wasm_bindgen_test]
async fn smoke() {
wasm_bindgen_futures::JsFuture::from(call_exports())
.await
.unwrap();
call_exports().await.unwrap();
}

#[wasm_bindgen]
Expand Down Expand Up @@ -70,3 +83,42 @@ pub async fn async_return_8() -> Result<AsyncCustomReturn, AsyncCustomReturn> {
pub async fn async_throw() -> Result<(), js_sys::Error> {
Err(js_sys::Error::new("async message"))
}

#[wasm_bindgen_test]
async fn test_promise() {
assert_eq!(call_promise().await.as_string(), Some(String::from("ok")))
}

#[wasm_bindgen_test]
async fn test_promise_ok() {
assert_eq!(
call_promise_ok().await.map(|j| j.as_string()),
Ok(Some(String::from("ok")))
)
}

#[wasm_bindgen_test]
async fn test_promise_err() {
assert_eq!(
call_promise_err().await.map_err(|j| j.as_string()),
Err(Some(String::from("error")))
)
}

#[wasm_bindgen_test]
async fn test_promise_unit() {
call_promise_unit().await
}

#[wasm_bindgen_test]
async fn test_promise_ok_unit() {
call_promise_ok_unit().await.unwrap()
}

#[wasm_bindgen_test]
async fn test_promise_err_unit() {
assert_eq!(
call_promise_err_unit().await.map_err(|j| j.as_string()),
Err::<(), _>(Some(String::from("error")))
)
}