预期的 `()`,发现异步函数的不透明类型

expected `()`, found opaque type for async function

我在主范围内关注 a guide for setting up a WebRTC data-channel with web-sys. I can copy and paste the code and it compiles correctly. The start() function is async which makes it possible to await a JsFuture,但是我试图将此 await 移动到 onmessage_callback 块。只需将这一行添加到原始实现中,我就有了:

  let onmessage_callback =
        Closure::wrap(
            Box::new(move |ev: MessageEvent| {
                let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
                match ev.data().as_string() {
                    Some(message) => {
                        console_warn!("{:?}", message);
                        dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
                    }
                    None => {}
                }
            }) as Box<dyn FnMut(MessageEvent)>,
        );
    dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
    onmessage_callback.forget();

一旦我编译它,我当然会收到一条错误消息,指出等待未来 desc 只能在异步上下文中进行。我想我可以在定义 FnMut 函数时添加 async 关键字:

  let onmessage_callback =
        Closure::wrap(
            Box::new(move |ev: MessageEvent| async { // <-- async
                let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
                match ev.data().as_string() {
                    Some(message) => {
                        console_warn!("{:?}", message);
                        dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
                    }
                    None => {}
                }
            }) as Box<dyn FnMut(MessageEvent)>,
        );
    dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
    onmessage_callback.forget();

但是当我编译这个时,我得到以下错误:

error[E0271]: type mismatch resolving `<[closure@src/lib.rs:48:22: 57:14] as FnOnce<(MessageEvent,)>>::Output == ()`
  --> src/lib.rs:48:13
   |
48 | /             Box::new(move |ev: MessageEvent| async {
49 | |                 let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
50 | |                 match ev.data().as_string() {
51 | |                     Some(message) => {
...  |
56 | |                 }
57 | |             }) as Box<dyn FnMut(MessageEvent)>,
   | |______________^ expected `()`, found opaque type
   |
   = note: expected unit type `()`
            found opaque type `impl Future<Output = [async output]>`
   = note: required for the cast to the object type `dyn FnMut(MessageEvent)`

我不确定如何进行此操作,我认为错误是说回调 returns 一个未来,但它应该是无效的。

如何定义异步回调?

你通过说

声明你的函数不会return任何东西
Box<dyn FnMut(MessageEvent)>
//which is the same thing as Box<dyn FnMut(MessageEvent)->()>

然后在你的回调中,你实际上 return 返回一个异步块。

我不熟悉相关库,但查看文档时您不应该传递异步函数。

如果您必须使用异步并且如果您已经有运行时运行,您可以在同步块中生成一个任务。

但在 javascript 世界中,如果某些东西是异步的,你不能真正等待它,你只能给它一个回调(async/await 关键字存在,但它们所做的只是 return 你另一个承诺)

所以我相信你应该在这里使用 promise API 并为 then 方法提供回调。

这不是很漂亮,但这就是 js 世界中事情的完成方式,你在这里面对的是 js 中的 callback hell,这是一个东西 :D

Ps: Javascript 有一个单线程运行时,事情被安排在一个永远的 运行 循环上,称为事件循环。每次你在某处传递回调时,它都会在事件循环中注册并最终被调用。

重要的是要理解它的调用部分是无法保证的,如果您要在一个回调中阻止事件循环,那么循环的下一个滴答将永远不会到来,因此整个运行时将被锁定。这就是导致回调地狱的设计决策背后的原因

Pps:

我真的不认为你应该将 rust 异步运行时与你的 wasm 二进制文件捆绑在一起(如果可能的话),因为正如我提到的那样,js 有它自己的,只是坚持承诺 api

原因如@nikoss 所述,您传递了一个 future-returning 函数并将其转换为 unit-returning 函数。

至于如何解决这个问题,您可以使用 spawn_local():

在 JS promise 微任务队列上生成未来
let (pc1_clone, dc1_clone) = (pc1.clone(), dc1.clone());
let onmessage_callback =
    Closure::wrap(
        Box::new(move |ev: MessageEvent| {
            wasm_bindgen_futures::spawn_local(async move {
                let desc = JsFuture::from(pc1_clone.create_offer()).await.unwrap();
                match ev.data().as_string() {
                    Some(message) => {
                        console_warn!("{:?}", message);
                        dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
                    }
                    None => {}
                }
            });
        }) as Box<dyn FnMut(MessageEvent)>,
    );
dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();