Skip to content

Commit 98d7919

Browse files
committed
Implement extern "C" async functions.
It converts a JS Promise into a wasm_bindgen_futures::JsFuture that implements Future<Result<JsValue, JsValue>>.
1 parent 1edd43a commit 98d7919

File tree

4 files changed

+151
-15
lines changed

4 files changed

+151
-15
lines changed

crates/backend/src/codegen.rs

+48-11
Original file line numberDiff line numberDiff line change
@@ -971,22 +971,52 @@ 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 = quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
976+
let future = quote! {
977+
wasm_bindgen_futures::JsFuture::from(
978+
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
979+
::from_abi(#ret_ident)
980+
).await
981+
};
982+
convert_ret = if self.catch {
983+
quote! { Ok(#future?) }
984+
} else {
985+
quote! { #future.expect("unexpected exception") }
986+
};
987+
} else {
988+
abi_ret = quote! {
989+
<#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
990+
};
991+
convert_ret = quote! {
992+
<#ty as wasm_bindgen::convert::FromWasmAbi>
993+
::from_abi(#ret_ident)
994+
};
995+
}
981996
}
982997
None => {
983-
abi_ret = quote! { () };
984-
convert_ret = quote! { () };
998+
if self.function.r#async {
999+
abi_ret = quote! { <js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>::Abi };
1000+
let future = quote! {
1001+
wasm_bindgen_futures::JsFuture::from(
1002+
<js_sys::Promise as wasm_bindgen::convert::FromWasmAbi>
1003+
::from_abi(#ret_ident)
1004+
).await
1005+
};
1006+
convert_ret = if self.catch {
1007+
quote! { #future?; Ok(()) }
1008+
} else {
1009+
quote! { #future.expect("uncaught exception"); }
1010+
};
1011+
} else {
1012+
abi_ret = quote! { () };
1013+
convert_ret = quote! { () };
1014+
}
9851015
}
9861016
}
9871017

9881018
let mut exceptional_ret = quote!();
989-
if self.catch {
1019+
if self.catch && !self.function.r#async {
9901020
convert_ret = quote! { Ok(#convert_ret) };
9911021
exceptional_ret = quote! {
9921022
wasm_bindgen::__rt::take_last_exception()?;
@@ -1045,12 +1075,17 @@ impl TryToTokens for ast::ImportFunction {
10451075
&self.rust_name,
10461076
);
10471077

1078+
let maybe_async = if self.function.r#async {
1079+
Some(quote!{async})
1080+
} else {
1081+
None
1082+
};
10481083
let invocation = quote! {
10491084
#(#attrs)*
10501085
#[allow(bad_style)]
10511086
#[doc = #doc_comment]
10521087
#[allow(clippy::all)]
1053-
#vis fn #rust_name(#me #(#arguments),*) #ret {
1088+
#vis #maybe_async fn #rust_name(#me #(#arguments),*) #ret {
10541089
#extern_fn
10551090

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

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

+55-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,26 @@ use wasm_bindgen_test::*;
33

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

923
#[wasm_bindgen_test]
1024
async fn smoke() {
11-
wasm_bindgen_futures::JsFuture::from(call_exports())
12-
.await
13-
.unwrap();
25+
call_exports().await.unwrap();
1426
}
1527

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

0 commit comments

Comments
 (0)