Skip to content

Commit 6b3d730

Browse files
authored
Implement extern "C" async functions. (#2196)
* Implement extern "C" async functions. It converts a JS Promise into a wasm_bindgen_futures::JsFuture that implements Future<Result<JsValue, JsValue>>. * Run rustfmt. Add #[rustfmt::skip] to the tests/wasm/futures.rs because it removes the async from extern "C" blocks.
1 parent 31c2d6f commit 6b3d730

File tree

4 files changed

+154
-15
lines changed

4 files changed

+154
-15
lines changed

crates/backend/src/codegen.rs

+50-11
Original file line numberDiff line numberDiff line change
@@ -971,22 +971,54 @@ impl TryToTokens for ast::ImportFunction {
971971
);
972972
}
973973
Some(ref ty) => {
974-
abi_ret = quote! {
975-
<#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
976-
};
977-
convert_ret = quote! {
978-
<#ty as wasm_bindgen::convert::FromWasmAbi>
979-
::from_abi(#ret_ident)
980-
};
974+
if self.function.r#async {
975+
abi_ret =
976+
quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
977+
let future = quote! {
978+
wasm_bindgen_futures::JsFuture::from(
979+
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
980+
::from_abi(#ret_ident)
981+
).await
982+
};
983+
convert_ret = if self.catch {
984+
quote! { Ok(#future?) }
985+
} else {
986+
quote! { #future.expect("unexpected exception") }
987+
};
988+
} else {
989+
abi_ret = quote! {
990+
<#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
991+
};
992+
convert_ret = quote! {
993+
<#ty as wasm_bindgen::convert::FromWasmAbi>
994+
::from_abi(#ret_ident)
995+
};
996+
}
981997
}
982998
None => {
983-
abi_ret = quote! { () };
984-
convert_ret = quote! { () };
999+
if self.function.r#async {
1000+
abi_ret =
1001+
quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
1002+
let future = quote! {
1003+
wasm_bindgen_futures::JsFuture::from(
1004+
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
1005+
::from_abi(#ret_ident)
1006+
).await
1007+
};
1008+
convert_ret = if self.catch {
1009+
quote! { #future?; Ok(()) }
1010+
} else {
1011+
quote! { #future.expect("uncaught exception"); }
1012+
};
1013+
} else {
1014+
abi_ret = quote! { () };
1015+
convert_ret = quote! { () };
1016+
}
9851017
}
9861018
}
9871019

9881020
let mut exceptional_ret = quote!();
989-
if self.catch {
1021+
if self.catch && !self.function.r#async {
9901022
convert_ret = quote! { Ok(#convert_ret) };
9911023
exceptional_ret = quote! {
9921024
wasm_bindgen::__rt::take_last_exception()?;
@@ -1045,12 +1077,17 @@ impl TryToTokens for ast::ImportFunction {
10451077
&self.rust_name,
10461078
);
10471079

1080+
let maybe_async = if self.function.r#async {
1081+
Some(quote! {async})
1082+
} else {
1083+
None
1084+
};
10481085
let invocation = quote! {
10491086
#(#attrs)*
10501087
#[allow(bad_style)]
10511088
#[doc = #doc_comment]
10521089
#[allow(clippy::all)]
1053-
#vis fn #rust_name(#me #(#arguments),*) #ret {
1090+
#vis #maybe_async fn #rust_name(#me #(#arguments),*) #ret {
10541091
#extern_fn
10551092

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

guide/src/reference/js-promises-and-rust-futures.md

+25
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ Here we can see how converting a `Promise` to Rust creates a `impl Future<Output
2525
= Result<JsValue, JsValue>>`. This corresponds to `then` and `catch` in JS where
2626
a successful promise becomes `Ok` and an erroneous promise becomes `Err`.
2727

28+
You can also import a JS async function directly with a `extern "C"` block, and
29+
the promise will be converted to a future automatically. For now the return type
30+
must be `JsValue` or no return at all:
31+
32+
```rust
33+
#[wasm_bindgen]
34+
extern "C" {
35+
async fn async_func_1() -> JsValue;
36+
async fn async_func_2();
37+
}
38+
```
39+
40+
The `async` can be combined with the `catch` attribute to manage errors from the
41+
JS promise:
42+
43+
```rust
44+
#[wasm_bindgen]
45+
extern "C" {
46+
#[wasm_bindgen(catch)]
47+
async fn async_func_3() -> Result<JsValue, JsValue>;
48+
#[wasm_bindgen(catch)]
49+
async fn async_func_4() -> Result<(), JsValue>;
50+
}
51+
```
52+
2853
Next up you'll probably want to export a Rust function to JS that returns a
2954
promise. To do this you can use an `async` function and `#[wasm_bindgen]`:
3055

tests/wasm/futures.js

+23
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,26 @@ exports.call_exports = async function() {
1414
assert.strictEqual(8, (await wasm.async_return_8()).val);
1515
await assert.rejects(wasm.async_throw(), /async message/);
1616
};
17+
18+
exports.call_promise = async function() {
19+
return "ok";
20+
}
21+
22+
exports.call_promise_ok = async function() {
23+
return "ok";
24+
}
25+
26+
exports.call_promise_err = async function() {
27+
throw "error";
28+
}
29+
30+
exports.call_promise_unit = async function() {
31+
console.log("asdfasdf");
32+
}
33+
34+
exports.call_promise_ok_unit = async function() {
35+
}
36+
37+
exports.call_promise_err_unit = async function() {
38+
throw "error";
39+
}

tests/wasm/futures.rs

+56-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
use wasm_bindgen::prelude::*;
22
use wasm_bindgen_test::*;
33

4+
#[rustfmt::skip]
45
#[wasm_bindgen(module = "tests/wasm/futures.js")]
56
extern "C" {
6-
fn call_exports() -> js_sys::Promise;
7+
#[wasm_bindgen(catch)]
8+
async fn call_exports() -> Result<JsValue, JsValue>;
9+
10+
async fn call_promise() -> JsValue;
11+
#[wasm_bindgen(catch)]
12+
async fn call_promise_ok() -> Result<JsValue, JsValue>;
13+
#[wasm_bindgen(catch)]
14+
async fn call_promise_err() -> Result<JsValue, JsValue>;
15+
16+
#[wasm_bindgen]
17+
async fn call_promise_unit();
18+
#[wasm_bindgen(catch)]
19+
async fn call_promise_ok_unit() -> Result<(), JsValue>;
20+
#[wasm_bindgen(catch)]
21+
async fn call_promise_err_unit() -> Result<(), JsValue>;
722
}
823

924
#[wasm_bindgen_test]
1025
async fn smoke() {
11-
wasm_bindgen_futures::JsFuture::from(call_exports())
12-
.await
13-
.unwrap();
26+
call_exports().await.unwrap();
1427
}
1528

1629
#[wasm_bindgen]
@@ -70,3 +83,42 @@ pub async fn async_return_8() -> Result<AsyncCustomReturn, AsyncCustomReturn> {
7083
pub async fn async_throw() -> Result<(), js_sys::Error> {
7184
Err(js_sys::Error::new("async message"))
7285
}
86+
87+
#[wasm_bindgen_test]
88+
async fn test_promise() {
89+
assert_eq!(call_promise().await.as_string(), Some(String::from("ok")))
90+
}
91+
92+
#[wasm_bindgen_test]
93+
async fn test_promise_ok() {
94+
assert_eq!(
95+
call_promise_ok().await.map(|j| j.as_string()),
96+
Ok(Some(String::from("ok")))
97+
)
98+
}
99+
100+
#[wasm_bindgen_test]
101+
async fn test_promise_err() {
102+
assert_eq!(
103+
call_promise_err().await.map_err(|j| j.as_string()),
104+
Err(Some(String::from("error")))
105+
)
106+
}
107+
108+
#[wasm_bindgen_test]
109+
async fn test_promise_unit() {
110+
call_promise_unit().await
111+
}
112+
113+
#[wasm_bindgen_test]
114+
async fn test_promise_ok_unit() {
115+
call_promise_ok_unit().await.unwrap()
116+
}
117+
118+
#[wasm_bindgen_test]
119+
async fn test_promise_err_unit() {
120+
assert_eq!(
121+
call_promise_err_unit().await.map_err(|j| j.as_string()),
122+
Err::<(), _>(Some(String::from("error")))
123+
)
124+
}

0 commit comments

Comments
 (0)