Consideremos un objeto de importación de ejemplo que se ve así:
const importObject = { exampleAsyncImportFunction: async () => fs.readFile("./someExampleFile.txt", "utf-8") // returns Promise<String> };
Quiero usarlo en mi módulo WASM escrito en óxido similar a este:
#[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) }
Desafortunadamente, esto conduce a un error inalcanzable. Curiosamente, funciona cuando la función JavaScript devuelve, por ejemplo, una cadena, como esta:
const importObject = { exampleAsyncImportFunction: async () => "Some dummy content" // returns Promise<String> as well };
Pero, por supuesto, una función de importación asíncrona debería realizar algunas tareas asíncronas reales; de lo contrario, sería mejor usar una función síncrona.
Traté de investigar un poco y encontré 'js_sys::Promise' que representa una JS-Promise en Rust, y 'wasm_bindgen_futures::JsFuture' que permite convertir de alguna manera una JS-Promise en una Rust-Future. Pero no entiendo si/cómo estos tipos pueden ayudar con el problema y también cómo deben usarse en general en este contexto.
También parece que el tipo de retorno de JS-Import-Function, que es 'JsValue' por declaración, de alguna manera implementa el 'Future'-Trait. Entiendo que por eso es posible 'esperar' el resultado. Pero estoy confundido, ¿cuál es el tipo subyacente real (JsFuture, Promise, algo más...).
Espero que alguien pueda ayudarme con esto, resolviendo el problema real, pero también explicando un poco las relaciones entre todos los tipos (especialmente con respecto a JsValue).
¡Gracias de antemano!
Encontré la solución yo mismo, y como parece que no hay buena información en otros lugares de Internet, quiero compartir mi solución con cualquiera que tenga problemas similares.
Para importar la función JavaScript, es importante actuar como si la función fuera síncrona, aunque es asíncrona. La razón de esto es que las funciones asíncronas en Rust solo se ejecutan cuando se usa un ejecutor o una espera. Por lo tanto, la función async-JS, que bajo el capó es una función síncrona que devuelve una Promesa, se puede usar de forma síncrona. La promesa devuelta se puede llamar explícitamente.
La sección de importación tendrá el siguiente aspecto:
#[wasm_bindgen(module = "/some/file.js")] extern "C" { pub fn exampleAsyncImportFunction() -> JsValue; // JsValue <==> Promise // Instead of: // pub async fn exampleAsyncImportFunction() -> Result<JsValue, JsValue> }
Para consumir la función de importación, Promise como JsValue debe convertirse en js_sys::Promise y luego en wasm_bindgen_futures::JsFuture. Después de eso, se puede esperar como se esperaba.
La función de exportación de muestra de las preguntas originales se verá así:
#[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 } }
Espero que este enfoque, específicamente la transformación de JsValue a JsFuture, ayude a algunos de ustedes, ya que los documentos oficiales no cubren ese caso de uso, sino solo la instanciación manual de un js_sys::Promise (por ejemplo, Promise::resolve( &"foo".into()) ) o usando web_sys::window::fetch().