Uptonian Thoughts

The Handle Pattern

· ·

I’m not entirely sure if there’s a better name for this pattern that already exists, but I like “handle pattern” to describe this method of keeping track of and managing “subscriptions”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var subscriber = (function() {

    var _listeners = {};

    var s = {
        // Listen to a channel and call a callback when that channel fires
        listen: function(channel, cb) {
            // Add the callback to the list of listeners on the given channel
            _listeners[channel] = _listeners[channel] || [];
            _listeners[channel].push(cb);

            return {
                // Return an object that can be used to remove the callback from the channel
                unlisten: function() {
                    // Remove the callback from the list of listeners on that channel
                    _listeners[channel].splice(_listeners[channel].indexOf(cb), 1);
                }
            }
        },

        // Manually fire events on a given channel.
        publish: function(channel) {
            _listeners[channel] = _listeners[channel] || [];
            _listeners[channel].forEach(function(cb) {
                cb();
            });
        }
    };

    return s;
})();

var h = subscriber.listen('update', function() {
    console.log('The update event was fired!');
});

subscriber.publish('update'); // > "The update event was fired!"

h.unlisten();

subscriber.publish('update'); // > <no output>

When you have an event-driven application, like a Javascript app that performs actions based on user interaction or based on back end “pushes” to a listening front end, you often have a central “publisher” that handles firing events when certain actions occur. It makes sense to have a static listen function that takes a “channel” and a callback function to call when that channel gets updated. The problem comes when you have to decide how to stop listening to that channel. If you go with a static unlisten function on the publisher with the same signature as listen (the channel and callback), you need to keep track of which callback is listening to which channel, and it can get messy.

Instead, listen can return a handle, which is just an object that contains a method unlisten that knows exactly how to stop listening on the specific channel and with the specific callback that was given to listen. Then, the caller just needs to keep track of the return values of listen (as opposed to the arguments to listen) in order to be able to unlisten later.

The Dojo Stateful interface uses this pattern to watch values on objects. If you watch a property, a callback can be fired each time that property changes. The return value of watch is a handle that can be used to stop watching that particular property value.

Feel free to play around with this code on JSFiddle.