2011.11.10

js-signals v0.7.0 and CompoundSignal

Last week I released a new version of js-signals, I usually avoid creating posts at each release since I think people can simply watch the project on Github or are already following me on twitter (and I always send a twit after each release) but I added a few “advanced” features that may be useful on specific cases, that’s why I decided to create a post about it, I think some explanation may help people identify good use cases for those features.

SignalBinding.params (a.k.a non-semantical versioning at v0.6.3)

v0.6.3 was supposed to be v0.7.0 and v0.7.0 was supposed to be v0.8.0, my bad… I changed the API a little bit from v0.6.2 to v0.6.3 and still released it as a “patch version” (see semantic versioning for more info). So I’m going to also talk about one simple feature I added on v0.6.3 (it was an one line change and I was already using it, that’s why I released as 0.6.3).

When adding a new listener to a signal you have the option to set an Array that will be passed to the listener during execution. e.g.:

var loaded = new signals.Signal();

var loadedBinding = loaded.add(console.log, console);
//set the default parameters of binding
loadedBinding.params = ['lorem', 'ipsum'];

//will log "lorem ipsum dolor"
loaded.dispatch('dolor');

This can be very useful when you are adding listeners dynamically and want to pass some info to the listener, since js-signals already uses apply to set the execution context (this object inside handler) the implementation was very simple (updated 1 line of code) and it increased the flexibility a lot. Of course this feature shouldn’t be abused since you need to know the structure of the event handler and which parameters it does expect, but it is a nice and concise way to pass parameters and it can avoid creating a new function just for that need (on a previous project I had to wrap some functions just for that use case).

Signal.memorize / Signal.forget()

This is a feature that I’ve been pondering for a long time, it’s a way to listen to events that “happened in the past”, yep it sounds strange but that is what it does, it allows you to add a listener after the event was already dispatched and it will trigger the event handler automatically passing the previously dispatched arguments.

var started = new signals.Signal();

started.memorize = true; // default is false
started.dispatch('foo');

// add()/addOnce() will automatically fire listener if signal was dispatched before
// will log "foo" since it keep record of previously dispatched values
started.addOnce(console.log, console);

started.forget(); // forget previously dispatched values (reset signal state)
started.addOnce(console.log, console); // won't log till next dispatch (since it "forgot")
started.dispatch('bar'); // log "bar"

It can be useful for events like initialized, started, loaded, etc… It works similarly to a “promise” that was already “resolved”.

Initially I thought about implementing it as a new type of Signal, maybe called AsyncSignal or something like that, but at the end I decided to simply add the new property and method since implementation was way simpler/smaller and the usage as well, this is JavaScript not Java!!

Signal.has(listener)

Never really needed this feature on any project but since it was already being used internally and was just one line I think it is worth making it public. Maybe it can be useful to someone.

function onStart(a){
  console.log(a);
}
started.add(onStart);
started.has(onStart); // true

Universal Module Wrapper (UMD)

Until v0.6.3 js-signals was being distributed with separate files to each environment (nodejs, browser, AMD), since v0.7.0 you can use the same distribution file on all the environments and with almost no overhead, the boilerplate code is minimal and won’t affect performance.

CompoundSignal / listening to multiple Signals at once

I also spent a long time thinking about this feature, specially trying to decide if it should be merged into signals “core” or if it should be a separate project, at the end I decided to split it into it’s own repository since it isn’t that common to need this feature and it would make it easier to install it using npm. I coded an ad-hoc solution to do the same thing before on a project but once I realized I needed the exact same thing on another project I thought it was better to abstract it and create a new type of Signal that could handle it transparently and that would be more flexible as well.

The CompoundSignal works like a group of signals which will be dispatched automatically after all the signals contained by the group are dispatched. Arguments are passed to listeners as Arrays on the same order as the signals were passed to the constructor.

var endedAnimation = new signals.Signal();
var completedSomething = new signals.Signal();

// CompoundSignal is just a regular Signal that gets dispatched after the other
// signals are dispatched (and have a similar API)
var completedManyThings = new signals.CompoundSignal(endedAnimation, completedSomething);
completedManyThings.addOnce( doSomethingOnCompletedManyThings );

function doSomethingOnCompletedManyThings(paramsArr1, paramsArr2){
  //handler will receive parameters of each signal as arrays since
  //they can dispatch an arbitrary number of parameters
  console.log( paramsArr1, paramsArr2 );
}

completedSomething.dispatch('lorem', 'ipsum');

setTimeout(function(){
    endedAnimation.dispatch('ended animation');
}, 500);

//will log after 500ms: ['ended animation'], ['lorem', 'ipsum']
//note that params are on the same order as the signals were passed to
//CompoundSignal constructor not at the order they were dispatched

CompoundSignal is very powerful and flexible, although the implementation is actually very simple, check the project repository for more info and check the unit tests and source code for more details, it has some advanced features as well like overwriting previously dispatched values, dispatching same signal multiple times, resetting state, etc…

Nice thing to note is that CompoundSignal uses SignalBinding.params to simplify the logic. That’s exactly the kind of stuff that motivated me to add the SignalBinding.params, so simple and useful…

More

I hope the new additions and explanations help you to code better asynchronous applications and to decouple your objects. Signals improved the quality of my code a lot and also reduced the amount of typo errors and scope issues (the second parameter of add and addOnce can be used to set the value of the this object inside the listener), I hope it does the same for you.

Check more examples on the wiki and watch js-signals and CompoundSignal on github. For feature requests, suggestions and bug reports please use the github issue tracker. There is also an online documentation in case you need it…

Asynchronous programming is hard, use tools that make the process easier and that also provides enough flexibility.

That’s it for now.


Comments

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.