Node.js: how can I return a value from an event listener?
Asked Answered
P

3

21

How to i return value from the an event listeners?

See example below:

const EventEmitter = require("events").EventEmitter;
emitter = new EventEmitter();
emitter.on("sayHello", function(message) {
    return message + " World";
});
let helloMessage = emitter.emit("sayHello", "Hello");
console.log(helloMessage); // It should output: "Hello World"

I want to modify event value and return the modified version.

How do i do it?

Pericarp answered 15/3, 2017 at 6:54 Comment(1)
@PavelGatnar thanks. but i need to return the modified value.Pericarp
P
26

The return value of event handlers is completely ignored. From the documentation:

When the EventEmitter object emits an event, all of the functions attached to that specific event are called synchronously. Any values returned by the called listeners are ignored and will be discarded.

But what you can do instead is emit an object and have the callback modify the state of that object:

const EventEmitter = require("events").EventEmitter;
const emitter = new EventEmitter();
emitter.on("sayHello", function(e) {
    e.message += " World";              // Modifying the state
});
const e = {message: "Hello"};
emitter.emit("sayHello", e);
console.log(e.message); // "Hello World"

(Also note the changes creating an instance of EventEmitter; your original code tried to use on on EventEmitter itself, but it doesn't have one.)


You've said in the comments that you would like to avoid having to create the object as its own step. You can easily subclass EventEmitter to add your own emitObject (or whatever) function:

class MyEventEmitter extends EventEmitter {
    emitObject(event, obj = {}) {
        this.emit(event, obj);
        return obj;
    }
}

Usage:

const emitter = new MyEventEmitter();
emitter.on("sayHello", function(e) {
    e.message += " World";
});
const evt = emitter.emitObject("sayHello", {message: "Hello"});
console.log(evt.message); // "Hello World"

I've supplied an object for the second argument there (so we could do the "Hello" + " World" thing), but if you left it off, a blank object would be defaulted for it (ES2015 default arguments).

Complete example:

const EventEmitter = require("events").EventEmitter;
class MyEventEmitter extends EventEmitter {
    emitObject(event, obj = {}) {
        this.emit(event, obj);
        return obj;
    }
}
const emitter = new MyEventEmitter();
emitter.on("sayHello", function(e) {
    e.message += " World";
});
const evt = emitter.emitObject("sayHello", {message: "Hello"});
console.log(evt.message); // "Hello World"
Pearle answered 15/3, 2017 at 6:59 Comment(9)
Hi. thanks for the answer. but it is too hard. because each time i want to make an event i have to create a new object. is there any better way?Pericarp
@HasanBayat: It's trivial. And no, there isn't another way. You can wrap it up in a function, of course.Pearle
Is there any wordpress like hook API for Node.js?Pericarp
@HasanBayat: You can subclass EventEmitter and add your own function that creates the object for you.Pearle
Do you can add it to your answer? please. ;)Pericarp
@HasanBayat Done.Pearle
Thanks. that's nice. Can i override the emit function?Pericarp
@HasanBayat: Yes. Refer to any tutorial on class in ES2015 (aka "ES6"). I don't think I would, though, it would be misleading to others reading the code.Pearle
Thanks that helped me. :)Pericarp
A
2

If you just want to has a "return" value,just use callback function.

const MyEmitter = require("events");
const myEmitter = new MyEmitter();
myEmitter.on("aaa", (callback) => {
  callback("123");
});
(async () => {
  const result = await new Promise((resolve) =>
    myEmitter.emit("aaa", (value) => {
      resolve(value);
    })
  );
  console.log(`result`, result);
})();

or just add another listener as answer listener:

Alfy answered 29/9, 2022 at 7:4 Comment(0)
B
1

So, I was able to do this by creating a custom version of the EventEmitter. I added a new method to register the emitted event as pending and return a promise that will resolve when the listener/handler for that event returns a value.

class MessageBus {
  #emitter = new EventEmitter();
  #pendingEventsRegistry = new Map<
    string | symbol,
    Array<(value: any) => void>
  >();

  public send<T>(eventName: string, message: any) {
    const result$ = new Promise<T>((resolve) => {
      const resolvers = this.#pendingEventsRegistry.get(eventName) ?? [];
      resolvers.push(resolve);
      this.#pendingEventsRegistry.set(eventName, resolvers);
    });
    this.#emitter.emit(eventName, message);
    return result$;
  }

  #execute(eventName: string | symbol, result: any) {
    const resolvers = this.#pendingEventsRegistry.get(eventName) ?? [];
    for (const resolve of resolvers) {
      resolve(result);
    }
    this.#pendingEventsRegistry.delete(eventName);
  }

  public on(eventName: string | symbol, listener: (...args: any[]) => void) {
    this.#emitter.on(eventName, (...args: any[]) => {
      const result = listener(...args);
      this.#execute(eventName, result);
    });
  }

  public once(eventName: string | symbol, listener: (...args: any[]) => void) {
    this.#emitter.once(eventName, (...args: any[]) => {
      const result = listener(...args);
      this.#execute(eventName, result);
    });
  }
}
Bobbie answered 5/12, 2022 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.