Emitter

facebook

by Paco86

HTML

<!-- const emitter = new Emitter();

1.support subscribing to events.
const sub = emitter.subscribe('event_name', callback);

2. support emitting events
It should lead to the callback being invoked with foo and bar as parameters
emitter.emit('event_name', 'foo', 'bar');

3. support unubscribing exixting subscriptions by releasing them 
sub.release();
如果一个函数被 subscribe 多次,如何根据返回的 subscription,delete 的时候删除特定的函数,而不是两个相同的函数随便删除一个。

几个follow up:
因为我一开始是把subscribe后返回的东西直接先定义一个class再new:
面试官问What if we don’t want to use another class object? 考察了稍微进阶一点的prototype用法,另一篇面经里有
What if the callback() function throws error? What will happen? (w/o using try...catch) 这题有点摸不清面试官要问啥,到底是问怎么更好的handle error,还是考察error发生后对app有什么影响。当时东扯西扯反正把我知道的都讲出来了,但自己感觉答的有点不organize -->

JavaScript

class Emitter {
	constructor() {
  	// listeners to save even with callbacks
  	this.listeners = new Map();
  }
	
	subscribe = (eventName, callback) => {
  		if (this.listeners.has(eventName)) {
      	var eventMap = this.listeners.get(eventName);
      	eventMap.set(callback, (eventMap.get(callback) || 0) + 1);
      } else {
      	this.listeners.set(eventName, new Map().set(callback, 1));
      }
      
      return {
      	release: () => {
        	if (this.listeners.has(eventName)) {
          	// only release one callback in 0(1)
            var eventList = this.listeners.get(eventName);

            var frequency = eventList.get(callback) - 1;
            if (frequency === 0) {
              eventList.delete(callback);          
            } else {
              eventList.set(callback, frequency);
            }

            //if empty, delete the listener
            if (eventList.size === 0) this.listeners.delete(eventName);
          }
        }
      }
  }
  
  emit = (eventName, ...args) => {
  	const listeners = this.listeners.get(eventName);
    if (listeners) {
    	listeners.forEach((frequency, callback) => {
      	try {
        	callback.apply(this, args);
        } catch(err) {
        	console.log(err);
        }
      });
    } else {
    	/* throw new Error('No emitter!'); */
      console.log(`You are not subscribed to ${eventName}`)
    }
  }
}

var cbFn = (a, b) => {console.log(a, b);};
const emitter = new Emitter();
const suba1 = emitter.subscribe('aaa', cbFn);
const suba2 = emitter.subscribe('aaa', cbFn);
/* const suba1 = emitter.subscribe('aaa', function cb1(a, b) {
  console.log(a, b);
});
const suba2 = emitter.subscribe('bbb', function cb2(a, b) {
  console.log(a + b);
}); */
/* const sub2 = emitter.subscribe('bbb', (a, b, c) => {
  console.log(a, b, c);
}); */
emitter.emit('aaa', 'foo', 'bar');
suba1.release()
emitter.emit('aaa', 'foo', 'bar');
/* suba1.release();
suba2.release(); */

/* suba1.release();
emitter.emit('aaa', 'foo',...