// signal := ( creator:(listener) => void ) => ( listener:(data) => void )
function signal(creator) {
return function listen(listener) {
// The magic, hand the listener to the creator
creator(listener)
}
}
// thats it
So the events that will be emited are defined at creation. Thats useful if you want to stop others from emiting events for you. And its usable like this:
// Create a clicks signal/stream/emiter
var clicks = signals(function(listener) {
window.addEventListener('click', listener, false)
})
// Listen to it, with console.log
clicks(console.log.bind(console, '=>'))
Any click event will now be passed to the listener, greate.
But spamming the console gets old rather quick, and we cant stop it. Lets add that ability.
// signal := ( creator:(listener) => end:() => void ) => ( listener:(data) => void )
function signal(creator) {
return function listen(listener) {
// Pass along the creators return, he must know how to stop
return creator(listener)
}
}
Thats just the signal, it does not know how to stop and the change did not fix anything. It just returns whatever the creator returns. But that is all it needs to do, the creator is the place that knows how to stop and we just let it return an end function, it takes no arguments and should stop the signal.
// Creators must return a ender function
var clicks = signal(function(listener) {
window.addEventListener('click', listener, false)
// Return a function that takes no args and stops the events
return function() {
window.removeEventListener('click', listener)
}
})
You listen to it the same way, but saves what it returns.
And in this example call end
5 sec later.
// Listen to it, with console.log. And save the end function
var endClicks = clicks(console.log.bind(console, '=>'))
setTimeout(endClicks, 5000)
//=> Click as much as you can for 5 sec!
Just like every other eventemiter out there (that i have found) this is a push based model. But unlike most other this model only alows for one listener. With very small modifications you can however make it a broadcasting signal, just listen to a unicast and keep a list of listeners around.
function broadcast(source) {
var listeners = [], started = false, endSource
function addListener(l) {
listeners.push(l)
// When the first listener is added, let the source boradcast
if (!started) {
started = true
endSource = source(boradcast)
}
return removeListener.bind(null, l) // `end` for this listener
}
function removeListener(toRemove) {
listeners = listeners.filter(function(l) {
return l !== toRemove
})
// This is optional, you may alow sources without listeners sometimes
if (!listeners.length) {
endSource()
}
}
function broadcast(v) {
listeners.forEach(function(l) { l(v) })
}
}
Pass any source signal to the above and it will return a boradcasting signal.
var uniSeconds = signal(function(listener) { setInterval(listener, 1000) })
var seconds = broadcast(uniSeconds)
Or you can make it a writable signal. One that lets anyone put values into the signal, not just the creator function.
var sig = signal(function(listener) {
// Expose the listener as a `.write` method
sig.write = listener
return function end() {
delete sig.write
}
})
// You cant write before you listen, since the write method is added when the
// listener is added
var end = sig(console.log.bind(console, 'writableSignal: '))
sig.write('Yup')
So that is the basics of the signal structure. It is very much like an event
emiter with only a single data
event. But the benefit is that you can controle
them way more, and once you have them they are just functions that take another
function. And like any function you can put them anyware, if you have models
you can put any well named signal on them, like todo.completed
, then add
the correct handler to that signal.