Reputation: 205
Let's consider an example import object that looks like this:
const importObject = {
exampleAsyncImportFunction: async () => fs.readFile("./someExampleFile.txt", "utf-8") // returns Promise<String>
};
I want to use it in my rust-written WASM-Module similar to this:
#[wasm_bindgen]
extern "C" {
pub async fn exampleAsyncImportFunction() -> JsValue; // Importing the JS-Function
}
#[wasm_bindgen]
pub async fn exampleExportFunction() -> Result<JsValue, JsValue> {
let theJSPromise = exampleAsyncImportFunction(); // Call the async import function
let promiseResult = theJSPromise.await; // Execute the async import function
// Do sth with the result
OK(JsValue::UNDEFINED)
}
Unfortunately, this leads to an unreachable error. Interestingly, it does work, when the JavaScript-Function returns e.g. a string, like this:
const importObject = {
exampleAsyncImportFunction: async () => "Some dummy content" // returns Promise<String> as well
};
But of course, an async import function should perform some actual asynchronous tasks, otherwise it would be better to use a synchronous function.
I tried to do some research and found 'js_sys::Promise' which represents a JS-Promise in Rust, and 'wasm_bindgen_futures::JsFuture' which enables to somehow convert a JS-Promise to a Rust-Future. But I don't understand, if/how these types may help with the problem and also how they should be used in general in this context.
Also it seems, that the return type of the JS-Import-Function, which is 'JsValue' by declaration, somehow implements the 'Future'-Trait. I understand that due to that, 'awaiting' the result is possible. But I'm confused, what's the actual underlying type (JsFuture, Promise, something else...).
I hope that someone can help me with this, by solving the actual problem, but also explaining a bit the relationships between all the types (especially regarding JsValue).
Thank you in advance!
Upvotes: 5
Views: 2653
Reputation: 205
I found the solution myself, and as there seems to be no good information elsewhere in the internet, I want to share my solution for anybody, who has similar issues.
For importing the JavaScript-Function, it is important to act like the function was synchronous, although it is asynchronous. The reason for that is, that async functions in Rust are only executed, when an executor or await is used. Therefore, the async-JS-Function, which under the hood is a synchronous function that returns a Promise can be used synchronously. The returned promise can then be called explicitly.
The import section will look like the following:
#[wasm_bindgen(module = "/some/file.js")]
extern "C" {
pub fn exampleAsyncImportFunction() -> JsValue; // JsValue <==> Promise
// Instead of:
// pub async fn exampleAsyncImportFunction() -> Result<JsValue, JsValue>
}
To consume the import function, the Promise as a JsValue has to be converted into a js_sys::Promise and then to a wasm_bindgen_futures::JsFuture. After that it can be awaited as expected.
The sample export function from the original questions will look like this:
#[wasm_bindgen]
pub async fn exampleExportFunction() {
// Get a reference to the import function as a 'JsValue'
let promiseAsJsValue = exampleAsyncImportFunction(); // No execution, yet
// Convert 'JsValue' to 'js_sys::Promise', which is a proxy-type for JS-Promises
let promise = js_sys::Promise::from(promiseAsJsValue);
// Convert 'js_sys::Promise' to 'wasm_bindgen_future::JsFuture'
let future = wasm_bindgen_future::JsFuture::from(promise);
// Actually execute the Promise/Future
let result: Result<JsValue, JsValue> = future.await;
// Interpret the result
if let Ok(content) = result {
// do sth with the content
}
}
I hope that this approach, specifically the transformation from JsValue to JsFuture, will help some of you, as the official docs don't cover that use case, but only the manual instantiation of a js_sys::Promise (e.g. Promise::resolve(&"foo".into()) ) or using web_sys::window::fetch().
Upvotes: 6