Search code examples
rustwebassemblyweb-sys

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


I am following 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 inside the main scope, however I am trying to move this await to the onmessage_callback block instead. Just by adding this one line to the original implementation I have this:

  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();

Once I compile this I ofcourse get an error saying that awaiting the future desc is only possible inside an asynchronous context. I figured that I could add the async keyword when defining the FnMut function:

  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();

But when I compile this I get the following error:

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)`

I am not sure how to proceed with this, I think the error is saying that the callback returns a future but it should be void instead.

How do I define an async callback?


Solution

  • The reason is as specified by @nikoss, you pass a future-returning function and cast it to a unit-returning function.

    As for how to solve that, you can spawn the future on JS promise microtasks queue with spawn_local():

    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();