0

I have been stuck on this issue for two days and no dice on other stack overflow posts. I could really use some help.

My goal is to create an event emitter that allows three functions. Subscribe, Unsubscribe, and Emit - where emit sends the passed arguments to all subscription functions.

The problem that I am running into is that I can't seem to pass just a function into the subscription. I have to pass an anonymous function in that calls the other function. subscribe(() => entities[1].eventEmitter.emit()) vs subscribe(entities[1].eventEmitter.emit). I don't seem to understand what the actual difference here is, and why they would reference different 'this' values. I thought the second example is a simplified version of the first? Anyone have an explanation on why the two versions reference different 'this' values?

Well, this is all fine and dandy as passing in an anonymous function works, but when I try to unsubscribe, I no longer have a reference to the anonymous function and therefore cannot remove it from the array. I know you could store the anonymous function in a variable, but that is not feasible because I an dynamically generating these Entities and there could be "infinite" entities. I.e. unknown size.

So I must be doing something wrong here. Either I am doing this very wrong and there is a much easier solution out there, or I am just missing something that could solve my problem. Welcome solutions could be a different solution altogether or a way to subscribe, unsubscribe, and emit successfully. Thank you! Please let me know if I can provide additional clarification.

class EventEmitter {
  constructor() {
    this.subscriptions = [];
  }

  subscribe(fn) {
    this.subscriptions.push(fn);
  }

  unsubscribe(fn) {
    this.subscriptions = this.subscriptions.filter(subscription => subscription !== fn);
  }

  emit(args) {
    // upon subscribing, you should get an error when doing trigger that amounts to
    // "message": "Uncaught TypeError: Cannot read property 'subscriptions' of undefined"
    // this happens because the line entities[0].eventEmitter.subscribe(entities[1].eventEmitter.emit);
    // with argument passed in entities[1].eventEmitter.emit refers to the window?
    // so this.subscriptions is not defined
    // console.log(this);
    console.log('emitting');
    this.subscriptions.forEach(subscription => subscription(args));
  }
}

class Entity {
  constructor()  {
    this.eventEmitter = new EventEmitter();
  }
}

const entities = [
  new Entity(),
  new Entity(),
];

document.getElementById('add').addEventListener('click', () => {
  // works, but requires an anonymous function that can't be removed
  entities[0].eventEmitter.subscribe(() => entities[1].eventEmitter.emit());
  
  // doesn't work because 'this' in the emit funciton refers to the window
  entities[0].eventEmitter.subscribe(entities[1].eventEmitter.emit);
}, false);

document.getElementById('trigger').addEventListener('click', () => {
  entities[0].eventEmitter.emit();
}, false);

document.getElementById('remove').addEventListener('click', () => {
  // how? ...
  // entities[0].eventEmitter.unsubscribe(entities[1].eventEmitter.emit);
}, false);
<button id='add'>add event listener</button>
<button id='trigger'>trigger emit on element 0</button>
<button id='remove'>remove event listener</button>
tuffant21
  • 343
  • 2
  • 8
  • 1
    You can put `this.emit = this.emit.bind(this);` in the `Entity` constructor, then you can reference each individual entity's method anywhere and subscribe/unsubscribe it elsewhere. – Bergi Nov 25 '20 at 09:36
  • Dear lord, it worked! This was perfect, thank you for your help! – tuffant21 Nov 25 '20 at 19:39

1 Answers1

1

Hmm what you can do is to use .bind() and to bind it to your eventemitter class

I have created an array that stores the methods.

class EventEmitter {
  constructor() {
    this.subscriptions = [];
  }

  subscribe(fn) {
    this.subscriptions.push(fn);
  }

  unsubscribe(fn) {
    this.subscriptions = this.subscriptions.filter(subscription => subscription !== fn);
  }

  emit(args) {
    // upon subscribing, you should get an error when doing trigger that amounts to
    // "message": "Uncaught TypeError: Cannot read property 'subscriptions' of undefined"
    // this happens because the line entities[0].eventEmitter.subscribe(entities[1].eventEmitter.emit);
    // with argument passed in entities[1].eventEmitter.emit refers to the window?
    // so this.subscriptions is not defined
    // console.log(this);
    this.subscriptions.forEach(subscription => {
       console.log("emit");
       subscription(args)
    });
  }
}

class Entity {
  constructor()  {
    this.eventEmitter = new EventEmitter();
  }
}

const entities = [
  new Entity(),
  new Entity(),
];

let methods = [];

document.getElementById('add').addEventListener('click', () => {
 
 
  let method = entities[1].eventEmitter.emit.bind(entities[1].eventEmitter)
  methods.push(method);
  entities[0].eventEmitter.subscribe(method);
}, false);

document.getElementById('trigger').addEventListener('click', () => {
  entities[0].eventEmitter.emit();
}, false);

document.getElementById('remove').addEventListener('click', () => {
   entities[0].eventEmitter.unsubscribe(methods.pop())
}, false);
<button id='add'>add event listener</button>
<button id='trigger'>trigger emit on element 0</button>
<button id='remove'>remove event listener</button>
Ifaruki
  • 10,439
  • 2
  • 7
  • 24