Skip to content

Commit

Permalink
Implement extern "C" async functions.
Browse files Browse the repository at this point in the history
It converts a JS Promise into a wasm_bindgen_futures::JsFuture that
implements Future<Result<JsValue, JsValue>>.
  • Loading branch information
rodrigorc committed Jun 28, 2020
1 parent 1edd43a commit 4dc5827
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 15 deletions.
59 changes: 48 additions & 11 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! { <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 +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 {
Expand Down Expand Up @@ -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! { <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";
}
59 changes: 55 additions & 4 deletions tests/wasm/futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 +82,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")))
)
}

0 comments on commit 4dc5827

Please sign in to comment.