wl - Whenable events
There are two kinds of events:
-
Reusable events which may happen many times, like a mouse click or
a keypress. When subscribing to such an event, one normally does not
care if an event has already been triggered in the past. One just
needs to react to the event each time it happens in the future.
-
One-off events which only happen once, for instance a page load
event, an ajax-request responce, a complete of a calculation
delegated to a worker, or an asynchronous function callback. For
this kind of events it matters if an event has already been
triggered at the moment of subscription (which means there should be
additional check). In latter case the listener should likely be
performed immediately.
Whenable is a design pattern targeted to simplify dealing with the
second kind of events by providing a special kind of listener
subscriber. When using that subscriber, one does not need to worry
about if an event has already been triggered, in this case the
listener is invoked immediately. Additionally, the subscriber may be
used several times to store additional listeners.
A good example of what could be simplified by using the Whenable
solution is the page onload event. Here is how one can listen to the
onload event in the traditional style:
if (document.readyState == "complete") {
doWhatWeNeed();
} else {
var origOnload = window.onload || function(){};
window.onload = function(){
origOnload();
doWhatWeNeed();
}
}
The code should be performed each time a new listener should react to
the onload event. Using a whenable subscriber everything above is
simplified to:
window.whenLoaded(doWhatWeNeed);
The code says: call the given function doWhatWeNeed()
if the page
is loaded, otherwise wait until the page is loaded, and then call the
function.
Listener subscribers which behave like explained above are
conventionally named starting with the when..
prefix and followed by
a past participle describing an event: whenLoaded()
,
whenCompleted()
, whenFailedToLoad()
and so on. The Whenable term
is also used to refer to a one-off event supporting this kind of
subscription.
Whenable pattern was inspired by
Promises, and
it is similar to Promises in that it also allows not to care about
when an event actually fires. But unlike Promises, Whenable is much
easier to use and understand, produces simplier code, and is more
general solution thus covering a wider range of use-cases.
This wl
library implements the Whenable
object, which represents
such an event and can be used to easily produce a whenable-style
subscriber.
Installation
For the web-browser environment — download the
distribution,
unpack it and load the wl.js
in a preferrable way. That is an
UMD module, thus for instance it may simply be loaded as a plain
JavaScript file using the <script>
tag:
<script src="wl/wl.js"></script>
For Node.js — install wl
with npm:
$ npm install wl
and then in your code:
var wl = require('wl');
Optionally you may load the script from the
distribution:
var wl = require('path/to/wl.js');
After the module is loaded, the wl.Whenable()
constructor is
available.
Usage
Constructing a Whenable event is simple:
var myWhenable = new wl.Whenable;
The object has the two methods. The emit()
method fires the event
and invokes the subscribed listeners:
myWhenable.emit();
The getSubscriber()
method returns a whenable-style subscriber
function:
var whenEventTriggered = myWhenable.getSubscriber();
The subscriber may later be reused to subscribe a listener to the
event:
whenEventTriggered(myListener);
The myListener()
will be invoked after the event is triggered. If
the event has already been triggered at the moment of subscription,
the listener is called immediately (yet asynchronously in order to
keep the flow consistent).
The methods of the Whenable
object (along with the Whenable
instance itself) are not supposed to be exposed to the event
user. Normally the Whenable
event is stored private and is emitted
by internal means. Instead the whenable subscriber function (returned
by the getSubscriber()
method) is to be provided to the user so that
he can attach listeners to the event.
When subscribing a listener, the context may be provided as a second
argument:
whenEventTriggered(myObject.someMethod, myObject);
Upon the event is triggered, the subscribed listeners are executed in
their respective contexts (if provided upon subscription).
Additionally, the emit()
method may take any set of arguments which
are simply forwarded as the arguments provided to the subscribed
listeners. This allows to supply the listeners with some details about
the event:
myWhenable.emit(result);
Examples
Here is an ordinary asynchronous function which executes a callback
after some time:
var doSomething = function(cb) {
setTimeout(cb, 1000);
}
Let us create a Whenable event representing the function completion:
var somethingWhenable = new wl.Whenable;
var initiateSomething = function() {
doSomething(function() {
somethingWhenable.emit();
});
}
var whenSomethingDone = somethingWhenable.getSubscriber();
Now there are the two functions:
-
initiateSomething()
starts the process which should lead to the
event emission in the future, and
-
whenSomethingDone()
, the whenable-style subscriber which may
subscribe as many listeners as needed, before or after the event
is emitted.
Those two functions may now be used separately.
Similarly, if there is an asynchronous routine with two outcomes, one
may prepare the two whenable events:
var doSomething = function(successCb, failureCb) {
var cb = function() {
try {
...
} catch(e) {
return failureCb();
}
successCb();
}
setTimeout(cb, 1000);
}
var success = new wl.Whenable;
var failure = new wl.Whenable;
var initiateSomething = function() {
doSomething(
function(){success.emit();},
function(){failure.emit();}
);
}
var whenSomethingSucceded = success.getSubscriber();
var whenSomethingFailed = failure.getSubscriber();
The code above provides the similar initiator function
initiateSomething()
, and the two whenable subscribers,
whenSomethingSucceeded()
and whenSomethingFailed()
which subscribe
a provided listener to the success or failure outcomes respectively.
Another example: here is the implementation of the magic
window.whenLoaded()
subscriber given in the beginning of this
text. The subscriber is used to react to the page load event:
var onloadWhenable = new wl.Whenable;
if (document.readyState == "complete") {
onloadWhenable.emit();
} else {
var origOnload = window.onload || function(){};
window.onload = function(){
origOnload();
onloadWhenable.emit();
}
}
window.whenLoaded = onloadWhenable.getSubscriber();