Skip to content

Instantly share code, notes, and snippets.

@pirfalt
Last active December 27, 2015 20:29
Show Gist options
  • Save pirfalt/7384653 to your computer and use it in GitHub Desktop.
Save pirfalt/7384653 to your computer and use it in GitHub Desktop.
Signals (one event, emiters)

Signals (one event, emiters)

// 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment