From 4dc58272728cbf559d9fd5d906e86fc989b7f339 Mon Sep 17 00:00:00 2001 From: Rodrigo Rivas Costa Date: Tue, 9 Jun 2020 19:05:18 +0200 Subject: [PATCH] Implement extern "C" async functions. It converts a JS Promise into a wasm_bindgen_futures::JsFuture that implements Future>. --- crates/backend/src/codegen.rs | 59 +++++++++++++++---- .../reference/js-promises-and-rust-futures.md | 25 ++++++++ tests/wasm/futures.js | 23 ++++++++ tests/wasm/futures.rs | 59 +++++++++++++++++-- 4 files changed, 151 insertions(+), 15 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index ad982ffa1a4f..b1b0fb8e3e1e 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -971,22 +971,52 @@ 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! { ::Abi }; + let future = quote! { + wasm_bindgen_futures::JsFuture::from( + + ::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! { ::Abi }; + let future = quote! { + wasm_bindgen_futures::JsFuture::from( + + ::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()?; @@ -1045,12 +1075,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 { @@ -1096,6 +1131,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! { ::describe(); }, None => quote! { <() as WasmDescribe>::describe(); }, }; diff --git a/guide/src/reference/js-promises-and-rust-futures.md b/guide/src/reference/js-promises-and-rust-futures.md index 4d740bdf2518..1598a1e83ba3 100644 --- a/guide/src/reference/js-promises-and-rust-futures.md +++ b/guide/src/reference/js-promises-and-rust-futures.md @@ -25,6 +25,31 @@ Here we can see how converting a `Promise` to Rust creates a `impl Future>`. 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; + #[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]`: diff --git a/tests/wasm/futures.js b/tests/wasm/futures.js index 20cec9a89be3..487a98a259f6 100644 --- a/tests/wasm/futures.js +++ b/tests/wasm/futures.js @@ -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"; +} diff --git a/tests/wasm/futures.rs b/tests/wasm/futures.rs index 906dcf58f1b1..a056f2d60241 100644 --- a/tests/wasm/futures.rs +++ b/tests/wasm/futures.rs @@ -3,14 +3,26 @@ use wasm_bindgen_test::*; #[wasm_bindgen(module = "tests/wasm/futures.js")] extern "C" { - fn call_exports() -> js_sys::Promise; + #[wasm_bindgen(catch)] + async fn call_exports() -> Result; + + async fn call_promise() -> JsValue; + #[wasm_bindgen(catch)] + async fn call_promise_ok() -> Result; + #[wasm_bindgen(catch)] + async fn call_promise_err() -> Result; + + #[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] @@ -70,3 +82,42 @@ pub async fn async_return_8() -> Result { 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"))) + ) +}