Backbone.js Event Aggregator and your garbage collector

The Event Aggregator pattern
provides centralized events, thus simplifying producer/consumer registration/interaction.
Its implementation using Backbone.Events looks something like this (assuming node.js)

var _ = require("underscore"),
    Backbone = require("backbone"),
    EA,
    Listener;


EA = _.extend({}, Backbone.Events);


Listener = Backbone.Model.extend({
    initialize: function () {
        var eventAggregator = this.get("eventAggregator");
        this.listenTo(eventAggregator, "something:ready", this.onSomethingReady);
    },
    onSomethingReady: function () {
        console.log("something ready from " + this.cid);
    }
});


var instance = new Listener({eventAggregator: EA});
EA.trigger("something:ready");

The idea is that we pass the aggregator to the listeners and they
are responsible for registering for any event of interest. The above outputs

something ready from c1

So, what happens when we run the following?

var instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");

instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");
something ready from c1
something ready from c1
something ready from c3

Garbage collection is not an excuse to forget about memory management

Yep! The Listener instance with cid c1 is still around, listening for events.
Can you spell memory leak? Turns out, the garbage collector can not collect this object,
because we passed a reference to the event aggregator the moment we registered using listenTo.
It will not be garbage collected until the event aggregator itself goes out of scope.

Avoiding it

Here are two patterns that can help

For recurring events, Stop listening before disposing an object

var instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");

if (instance) {
    instance.stopListening();
}

instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");

For one-off events, use listenToOnce instead of listenTo

var _ = require("underscore"),
    Backbone = require("backbone"),
    EventAggregator,
    Listener;


EA = _.extend({}, Backbone.Events);


Listener = Backbone.Model.extend({
    initialize: function () {
        var eventAggregator = this.get("eventAggregator");
        this.listenToOnce(eventAggregator, "something:ready", this.onSomethingReady);
    },
    onSomethingReady: function () {
        console.log("something ready from " + this.cid);
    }
});


var instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");

instance = new Listener({eventAggregator: EA});

EA.trigger("something:ready");

Published by pgk

Person

%d bloggers like this: