I am trying to implement node like EventEmitter struct with this library. One of the methods requires to return mutable self-reference such as on method so that a user can chain methods to register multiple listeners.
Is there any way to implement this feature with wasm-bindgen?
const eventEmitter = new EventEmitter();
eventEmitter.on('eventA', () => {...})
.on('eventB', () => {...})
eventEmitter.emit('eventA')
I get this error when I return mutable self-reference.
error: cannot return a borrowed ref with #[wasm_bindgen]
...
impl EventEmitter {
pub fn(&mut self, event_name: String, callback: js_sys::Function) -> &mut EventEmitter {
...
self
}
}
...
I also tried returning a new struct with updated fields but it easily causes null pointer error.
I would recommend against copying the JS API 1-to-1, because a lot of JS idioms don't work in Rust (and vice versa). Instead I recommend creating an API that fits in well with Rust.
And there's no need for the chaining functionality, since you can just do this:
let eventEmitter = EventEmitter::new();
eventEmitter.on("eventA", move |_| { ... });
eventEmitter.on("eventB", move |_| { ... });
However, if you really do want the chaining functionality, you should be able to achieve it like this:
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
mod raw {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "events")]
extern "C" {
#[wasm_bindgen(js_name = default)]
pub type EventEmitter;
#[wasm_bindgen(constructor)]
pub fn new() -> EventEmitter;
#[wasm_bindgen(js_class = "default", method)]
pub fn on(this: &EventEmitter, name: &str, listener: &js_sys::Function);
#[wasm_bindgen(js_class = "default", method)]
pub fn emit(this: &EventEmitter, name: &str, value: &JsValue) -> bool;
}
}
pub struct EventEmitter {
emitter: raw::EventEmitter,
}
impl EventEmitter {
pub fn new() -> Self {
Self {
emitter: raw::EventEmitter::new(),
}
}
pub fn on<F>(&mut self, name: &str, callback: F) -> &mut Self
where F: FnMut(JsValue) + 'static {
let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(JsValue)>);
self.emitter.on(name, closure.as_ref().unchecked_ref());
self
}
pub fn emit(&self, name: &str, value: &JsValue) -> bool {
self.emitter.emit(name, value)
}
}
Basically, you create some raw bindings (which don't include chaining), and then you create a Rust struct which wraps the raw bindings (and provides chaining).
This is a quite common technique which is used to create more "Rust-like" APIs on top of JavaScript, rather than exposing the raw JS APIs directly.
Emm, I also faced this problem and finally use some black magic to solve it by modifying js file:
fp = path.resolve(__dirname, './pkg/your_module.js');
const content = fs.readFileSync(fp, {encoding: 'utf8'})
.replace(/(@returns {void}\n\s+\*\/)\n\s+static (\S+?\([\s\S]+?){\n\s+([\s\S\n]+?)}/g, (_, comm, funh, funb) => {
return `${comm.replace('void', 'this')}
static ${funh} {
${funb.replace('return ', '')}return this;
}
`
});
fs.writeFileSync(fp, content);
Hope to help you.
I think @Pauan answered this well enough so closing, but feel free to comment if there are still issues!
@Pauan Thank you!