如何在node.js环境中从WebAssembly(Rust)调用异步JavaScript Import函数?

5

让我们考虑一个如下所示的导入对象示例:

const importObject = {
  exampleAsyncImportFunction: async () => fs.readFile("./someExampleFile.txt", "utf-8") // returns Promise<String>
};

我想在用Rust编写的WASM模块中使用它,类似于这样:
#[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)
}

不幸的是,这会导致一个无法访问的错误。有趣的是,当JavaScript函数返回一个字符串时,它确实可以工作,就像这样:

const importObject = {
  exampleAsyncImportFunction: async () => "Some dummy content" // returns Promise<String> as well
};

当然,一个异步导入函数应该执行一些实际的异步任务,否则最好使用同步函数。

我尝试进行了一些研究,并找到了 'js_sys::Promise'(在 Rust 中表示 JS-Promise)和 'wasm_bindgen_futures::JsFuture'(可以将 JS-Promise 转换为 Rust-Future)。但是我不明白这些类型如何帮助解决问题,以及它们在此上下文中应该如何使用。

此外,JS-Import-Function 的返回类型声明为 'JsValue',似乎也实现了 'Future' 特性。我理解由于此,可以 'await' 等待结果。但是我很困惑,实际的底层类型是什么(JsFuture、Promise 还是其他什么...)。

我希望有人可以帮助我解决这个问题,并在解释所有类型之间的关系方面进行一些说明(特别是关于 JsValue 的部分)。

预先感谢您!


你能详细说明一下如何使用wasm-bindgen将importObject连接到extern "C"函数吗?我猜你使用了wasm-pack,然后通过require模块来实现的? - frankenapps
@frankenapps 当然可以!我正在使用 wasm-pack,使用它构建 wasm 模块和粘合代码(使用 node.js 目标)。 为了导入函数,我使用一个名为“imports.js”的 Javascript 文件,其中包含 module.exports = { // The import functions like above }。在 rust 中,我像上面描述的那样导入 JS 函数。唯一的区别是,'extern "c"' 块被注释为 **#[wasm_bindgen(module = "/imports.js")]**,以引用 JS 文件。从主机端,我只需使用 await import("./path") 导入包含 WASM 模块的生成文件夹即可。 - Konrad Koschel
1个回答

6
我自己找到了解决方案,因为似乎在互联网上没有好的信息,所以我想分享我的解决方案给任何遇到类似问题的人。
对于导入JavaScript函数,重要的是像该函数是同步的一样进行操作,尽管它是异步的。这样做的原因是:在Rust中,异步函数只有在使用执行器或await时才会被执行。因此,底层是返回一个Promise的异步JS函数可以同步使用。随后,可以显式地调用返回的Promise。
导入部分将如下所示:
#[wasm_bindgen(module = "/some/file.js")]
extern "C" {
  pub fn exampleAsyncImportFunction() -> JsValue; // JsValue <==> Promise
  // Instead of:
  // pub async fn exampleAsyncImportFunction() -> Result<JsValue, JsValue>
}

为了使用 import 函数,需要将 Promise 作为 JsValue 转换为 js_sys::Promise,再转换为 wasm_bindgen_futures::JsFuture。之后就可以像预期的那样等待它。
原问题中的示例导出函数如下所示:
#[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
  }
}

我希望这种方法,具体来说是从JsValue转换为JsFuture,能够帮助到一些人,因为官方文档没有涵盖这种用例,只有手动实例化js_sys::Promise (例如 Promise::resolve(&"foo".into())) 或使用web_sys::window::fetch()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接