Simple event emitter does not fire an event handler if it is preceded by a '.once' event handler. Cannot understand why this happens.
const emitter = (host = {}, listeners = {}) => Object.assign(host, {
emit (event, data) {
(listeners[event] || []).forEach(h => h(data))
},
on (event, handler) {
if (!listeners[event]) listeners[event] = []
listeners[event].push(handler)
return () => host.off(event, handler)
},
once (event, handler) {
if (!listeners[event]) listeners[event] = []
listeners[event].push(function h () {
handler(...arguments)
host.off(event, h)
})
},
off (event, handler) {
const i = (listeners[event] || []).findIndex(h => h === handler)
if (i > -1) {
listeners[event].splice(i, 1)
if (!listeners[event].length) delete listeners[event]
}
}
})
// EXAMPLE
const e = emitter()
e.once('msg', msg => {
console.log('once.msg: ', msg)
})
e.on('msg', msg => { // <- not firing
console.log('on.msg: ', msg)
})
e.on('msg', msg => {
console.log('on_1.msg: ', msg)
})
e.emit('msg' ,'See me?')
The first 'on.msg' handler does not fire at all, however if the 'once.msg' handler is moved below the other two everything fires smoothly, why is this?
That is because (listeners[event] || []).forEach(h => h(data))
will use an indexer starting at 0
and incrementing it for each item. When you delete an item (when calling once
which calls off
) the indexing is wrong and will actually skip the next item in line.
Easiest way to remedy this is to make a copy of the events:
(listeners[event] || []).slice().forEach(h => h(data));