Home > Back-end >  Is there a better way than event listening for communication between different objects?
Is there a better way than event listening for communication between different objects?

Time:02-19

Let's say we have an emitter object implementing some logic and another different type object that implements some other logic depending on the events triggered by the emitter object. I guess, we can simply solve this by using function[pointer]s in the emitter and change their target by using a function that adds listener functions of the listener object like the code in the following. Even we can remove it. Like the DOM events, I can say.

Can you suggest a better way for this newbie from some other profession previously? Thanks in advance.

class Emitter {
  constructor() {
    this.event1 = this.empty;
    this.event2 = this.empty;
  }

  empty(){}

  someEmmitterLogic(input) {
    // arbitrary logic here before event1 occurs
    this.event1({message:"produced at event1", b:1});
    // maybe event2 can also occur here
    this.event2({message:"produced at event2", d:2});
  }

  addEvent(event, listener) {
    switch (event) {
      case "event1":
        this.event1 = listener;
        break;
      case "event2":
        this.event2 = listener;
        break;
      default:
        console.log("Sorry, we don't emit this event.");
    }
  }

  removeEvent(event) {
    switch (event) {
      case "event1":
        this.event1 = this.empty;
        break;
      case "event2":
        this.event2 = this.empty;
        break;
      default:
        console.log("Sorry, cannot remove an event we are not emitting.");
    }
  }  
}

class Listener{
    constructor(){
// Simple listeners outputting the event object to the console 
    this.listener1 = (e) => console.log(e.message);
    this.listener2 = (e) => console.log(e.message);
}
}
///////////////////////////////////////////////////////////////////////////
const emitter = new Emitter();
const listener = new Listener();

console.log("STATUS: not listening emitter");
emitter.someEmmitterLogic();

// Add listeners
emitter.addEvent("event1", listener.listener1);
emitter.addEvent("event2", listener.listener2);

console.log("STATUS: started listening emitter");
emitter.someEmmitterLogic();

console.log("say we don't need event1 anymore, let's remove it");
emitter.removeEvent("event1");
console.log("STATUS: listening event2 only");
emitter.someEmmitterLogic();
.as-console-wrapper {
    max-height: 100% !important;
}

Fiddle of the code above

CodePudding user response:

Events

Using events in this way is a well-established way to provide this kind of communication. If you look for existing implementations of event emitters like Node.js's or search for "publish/subscribe," you'll find a lot of prior art you can draw on.

Some notes:

  • Usually, you want to have a set of event handlers rather than allowing just one.
  • Generally, the emitter would wrap calls to event handlers in try/catch blocks so that a handler throwing an error doesn't prevent the emitter code from continuing to do its job (which is typically just to notify listeners of the event).
  • Some systems (including the DOM's) provide the same event object to all listeners, allowing a bit of cross-talk between them. Uncontrolled cross-talk is probably a bad idea, but some form of controlled cross-talk may be useful.
  • Similarly some systems (including the DOM's) provide a way for the event listeners to cancel the event, preventing it reaching other listeners.

Coroutines

Another way to do communication along these lines when there's sequence (in a very broad sense) to be observed is to use coroutines. In JavaScript, coroutines can be implemented using generators, which are most easily created via generator functions. A generator is an object that produces and consumes values in response to a call to its next method.

Here's a really simple generator that only produces (doesn't consume) values:

// Generator function (note the `*` after `function`) returns
// a generator. This generator provides a series of values in
// a given range (inclusive).
function* range(from = 1, to = 10) {
    console.log("Generator: Starting");
    for (let value = from; value <= to;   value) {
        console.log(`Generator: Ready with value ${value}`);
        yield value;
    }
}

// A popular way to consume generators (and iterables in general) is
// the `for-of` loop. When the generator/iterable is infinite
// as in our case, you need to be sure to break out of it at some point.
for (const value of range(1, 5)) {
    console.log(`for-of:    Received ${value}`);
}
console.log(`Done`);
.as-console-wrapper {
    max-height: 100% !important;
}

Note how the logic of the generator (really simple logic in this case, a for loop) and the consumer of it (again really simple, the for-of loop) are intermixed.

Generators are a great way to produce iterables like that one, but they can also consume values that are pushed to them. To push values to a generator, you call next directly rather than implicitly. Here's another simple example of a generator, but this one both produces and consumes values — it's a simple 1-100 number guessing game, with the generator in charge of picking the number and telling you how good your guess is:

function* hiddenNumber(from = 1, to = 100) {
    console.log("Generator: Starting");
    const size = (to - from   1);
    const num = Math.floor(Math.random() * size)   from;
    // console.log("cheat: "   num);
    let guess = yield `Pick a number between ${from} and ${to} (inclusive)`;
    while (guess !== num) {
        if (guess < num) {
            if (Math.abs(num - guess) >= size / 2) {
                guess = yield "REALLY low";
            } else {
                guess = yield "too low";
            }
        } else if (guess > num) {
            if (Math.abs(num - guess) >= size / 2) {
                guess = yield "REALLY high";
            } else {
                guess = yield "too high";
            }
        }
    }
    return "you got it!";
}

const prompt = document.getElementById("prompt");
const input = document.getElementById("input");
const button = document.getElementById("button");
const doGuess = () => {
    const num = input.valueAsNumber;
    const result = game.next(num);
    const answer = result.value;
    prompt.textContent = answer;
    if (result.done) {
        button.disabled = true;
    } else {
        input.focus();
        input.select();
    }
};
const game = hiddenNumber();
prompt.textContent = game.next().value;
button.addEventListener("click", doGuess);
input.addEventListener("keypress", event => {
    if (event.key === "Enter") {
        doGuess();
    }
});
input.select();
<p id="prompt"></p>
<input id="input" type="number" value="0" step="1">
<input id="button" type="button" value="Guess">

You can see the interaction of the code using it (doGuess) with the generator's logic.

Coroutines are really useful, but (IMHO) probably less general than an event emitter.

CodePudding user response:

red lithe

enter link description here

  • Related