为什么 js_sys Promise::new 需要 FnMut?

问题描述 投票:0回答:1

js_sys
通过函数
Promise
公开 JavaScript
pub fn new(cb: &mut dyn FnMut(Function, Function)) -> Promise;
。根据我对 MDN 文档的阅读,没有任何迹象表明执行器函数将被多次调用,但是
js_sys
将其公开为
FnMut
而不是
FnOnce

因此,我无法专注于简单的任务,例如将来通过频道向自己发送消息。理想情况下,通道的

sender
一半将移入封闭区域,并且
Promise::new
将接受
FnOnce

async func send_to_future_self() {
  use std::sync::mpsc::{Sender, Receiver, channel};
  let (sender, receiver) = channel().split();

  // Schedule sending a message in the future

  // Option A: Move `sender` --> The closure becomes `FnOnce` because it consumes `sender`
  // Option B: Reference `sender` --> Then the borrow outlives `sender`'s lifetime

  let mut sleep_100 = move?? |accept: Function, _reject: Function| {
    // This Rust closure is exported to Javascript...
    let callback = Closure::__??__(move?? || {
      // Send a message via the channel
      sender.send(()).unwrap();
      let result = js_sys::Array::new();
      // Doesn't matter what value is passed here, it's just for synchronization
      result.push(&JsValue::from(true)); 
      accept.apply(&JsValue::undefined(), &result).unwrap();
    });

    web_sys::window().unwrap()
      .set_timeout_with_callback_and_timeout_and_arguments_0(
         callback.as_ref().unchecked_ref(), 
        100
      ).unwrap();
    // ... so intentionally forget it, otherwise it will be dropped at the end of this block 
    // despite JS still referring to it.
    callback.forget();
  };

  // Convert JS Promise into a Rust future
  // This wants an FnMut(Function, Function)
  let future: JsFuture = js_sys::Promise::new(&mut sleep_100).into();
  let _ = future.await;
  receiver.try_recv().unwrap();
}
rust promise future webassembly wasm-bindgen
1个回答
0
投票

我怀疑它在内部被包装为 JavaScript 函数对象,可以多次调用(即使不会)。

解决此问题的最简单方法是让函数可多次调用。一种简单的方法是将消耗性值包装在

Option
中并使用
Option::take()
来获取该值 - 如果返回
None
则该函数被多次调用。

这里有一些示例代码说明了这种技术:

struct Consumable;

impl Consumable {
    fn consume(self) {}
}

fn requires_fnmut(_: impl FnMut()) { }

fn main() {
    let mut x = Some(Consumable);
    
    requires_fnmut(move || {
        let x = match x.take() {
            Some(v) => v,
            
            // If None we were called twice, so just bail.
            None => return,
        };
        
        x.consume();
    });
}

这里,

Consumable
与您的情况中的
Sender
类似:它公开了一个消耗接收器的方法。在闭包中的捕获变量上调用此函数将使闭包实现
FnOnce
而不是
FnMut

但是,请注意在

main()
中,我们将
Consumable
包裹在
Option
中并标记为
mut

let mut x = Some(Consumable);

现在我们可以在闭包中使用

match x.take()
来“解开”它,如果
take()
返回
None
,则从闭包返回,这表明该函数被第二次调用。

Option
获取价值后,您可以自由消费它。

© www.soinside.com 2019 - 2024. All rights reserved.