Home > Uncategorized > Knockout.Clear: Fully automatic cleanup in KnockoutJS 3.3

Knockout.Clear: Fully automatic cleanup in KnockoutJS 3.3

Note: This is the background discussion to a library called knockout.clear.

Introduction

Among the many libraries that try to help us manage the complexity of responsive web UIs, KnockoutJS takes a unique approach that ultimately relies on the classic observer pattern, but with the twist that it can automatically detect dependencies and do all subscribing for you. If you take full advantage of this, you end up with a pattern I call “eventless programming”, a concept which I explored in depth a while back by rebooting it in C#.

The fundamental problem with the Observer pattern

The observer pattern suffers from a problem known as the lapsed listener. In short, if thing A is dependent on thing B, the lifetime of A must be greater than or equal to the lifetime of B, because B is holding a reference to A on its list of observers. This means that if B lasts a very long time (say, forever), then A will too. The end result can be indistinguishable from a memory leak – the very thing that garbage collection is supposed to solve, dagnabbit.

When you are explicitly, manually writing code to subscribe, this is not a surprise: you wrote code to put something on a list, so you must write code to take it off the list. It’s still a pain, but it’s unsurprising.

Knockout’s surprising behaviour

On the contrary, in Knockout you don’t have to write that code. Give an observable containing a string, you can define an observable that contains the string in brackets:

var stringInBrackets = ko.computed(() => "[" + str() + "]);

Without you needing to say so, stringInBrackets has been silently placed on str‘s list of observers (called “subscribers” in Knockout), so when str changes, stringInBrackets gets recomputed. But what if str lives forever whereas stringInBrackets is an ephemeral bit of fluff that you cook up temporarily and then discard? Then you have a problem. And what’s worse, a counterintuitive one. It looks like I’m just getting the value of something. Why should that other thing stop my thing from getting cleaned up?

To solve it, you have to put this somewhere:

stringInBrackets.dispose();

When should you do that? When you are never going to need stringInBrackets again. This sounds easier to figure out than it sometimes is. In simple cases, it’s when you get rid of the DOM node that binds to it. But sometimes that’s only a temporary situation and you’ll later rebind it to another DOM node; if you’ve disposed it, it won’t work anymore.

Essentially, it’s the old problem of figuring out a hierarchical pattern of ownership, and making sure you don’t dispose too early, or not at all. Garbage collection is suppose to avoid this. Where you have the dispose pattern, you don’t have the benefits of GC.

Given this, it’s understandable that critics of Knockout sometimes accuse it of replacing one complexity problem with another.

Problem solved (mostly)

But in Knockout 3.2, an interesting new feature was added. We can change our code to say:

var stringInBrackets = ko.pureComputed(() => "[" + str() + "]);

Now when stringInBrackets is first created, it is asleep. Only when it gets its own first subscribe does it execute the evaluator function and become a subscriber to str, transitioning to being awake. Best of all, when stringInBrackets loses its final subscriber, it goes back to sleep, so it unsubscribes from str. Note that it can switch between the asleep/awake states as many times as required; this is in contrast to being disposed, which is a one-way street.

This makes all the difference in the world. Well-behaved UI bindings will take care of properly unsubscribing, which means that if your view model consists only of plain observables and pureComputeds you can wave goodbye to the lapsed listener problem!

Except…

… there’s a handy little trick you may have stumbled upon. I call it the “orphan computed”. It looks like this:

ko.computed(() => {
    items().forEach(item => {
        // do something with each item...
    });
});

It looks weird at first. It’s an observable that always has the value undefined. (In TypeScript, it’s of type void). And therefore nothing observes it; after all, what would be the point? So why do we need it? Because of its side-effects. It’s not a pure function that returns a value. It changes the state of other things. An example would be that it makes minimal updates to another structure (e.g. grouped items).

If you can live without such chicanery you have nothing to worry about. But realistically, side-effects are very handy. There’s a very important example on the Knockout wiki that shows how to automatically unwrap promises, bridging the synchronous and asynchronous worlds, but internally it uses an orphan computed.

Can we switch these to using pureComputed?

ko.pureComputed(() => {
    items().forEach(item => {
        // do something with each item...
    });
});

In a word: no. The name itself is a clue: pure functions don’t have side-effects. But it’s really not the side-effects that are the problem; it’s the fact that its an orphan. The pureComputed will begin in the asleep state. As nothing ever asks for its value, it never wakes up, so never executes its evaluator at all.

So pureComputed, which promised so much, would seem to be a bust. But hold your horses, my fine friend. What’s that I hear coming over the hill like so many tedious metaphors? It’s our old friend lateral thinking!

In many situations, the solution is simple: don’t make it an orphan. Make the view observe its value, and do nothing with it. You can do this with a trivial binding, which I call execute:

ko.bindingHandlers.execute = {
    init: function() {
        return { 'controlsDescendantBindings': true };
    },
    update: function (element, valueAccessor) {
        // Unwrap recursively - so binding can be to an array, etc.
        ko.toJS(valueAccessor());
    }
};
ko.virtualElements.allowedBindings.execute = true;

You can use it like any other binding in your template. If you use a comment binding, it will make no difference at all to the DOM structure:

<!-- ko execute: mySideEffects --><!-- /ko -->

You can give it any pureComputed (or an array of them) that you want to keep awake. It’s the easy-to-understand, kinda-hacky way to keep a pureComputed awake so you can enjoy its side effects. A lot of the time, this gets you where you want to be, and doesn’t involve any extra weird concepts. It’s just more of the same, and it’s very easy to get right.

An alternative for more demanding scenarios

With the arrival of Knockout 3.3 we get a subtle enhancement that is another game-changer.

An observable can emit other events besides change (which it emits to notify of its value changing). In 3.3 pureComputed now emits awake and asleep events, as described in issue #1576, so we can react to its state changing. I know, it doesn’t sound that earth-shattering at first, but we can use it to build a new utility, which I’ve taken to calling ko.execute.

Here it is in a simplified form:

ko.execute = function(pureComputed, evaluator, thisObj) {

    function wake() {
        if (!disposable) {
            disposable = ko.computed(function() {
                evaluator.call(thisObj);
            }
        }
    }

    var disposable;
    pureComputed.subscribe(wake, null, "awake");
    pureComputed.subscribe(function() {
        if (disposable) {
            disposable.dispose();
            disposable = null;
        }
    }, null, "asleep");
}

You use it to make orphans, just like you used to with ko.computed. The difference is that rather than having to remember to dispose at exactly the right time, instead you pass it a pureComputed which will keep your orphan awake:

ko.execute(stringInBrackets, () => {
    items().forEach(item => {
        // do something with each item...
    });
});

It’s an alternative to the execute binding where, instead of referring to your side-effector from a binding, you associate it with something else to which you’re already binding. I’m going to call the first argument to execute the nanny, because it wakes your orphan up and puts it to sleep again.

But it has two limitations:

  • The nanny must be a pureComputed. This is a slight pain; in Knockout 3.3 ordinary observables don’t fire events to tell you when they transition between asleep and awake.
  • Your ko.execute‘s evaluator function must not depend on its nanny.

The second restriction is perhaps surprising, but think about it: your orphan will stay awake if there are any subscriptions to its nanny. If the orphan itself subscribes to the nanny, it will keep itself awake, and it will be no different to a plain ko.computed.

The full implementation of ko.execute, which can be found here, checks for both these conditions, making it impossible to use it incorrectly without finding out.

Using ko.unpromise to tame your asynchronous calls

Now we can re-examine that important use-case I mentioned earlier, involving asynchronous calls. The Knockout wiki gives a simple example implementation:

function asyncComputed(evaluator, owner) {
    var result = ko.observable();

    ko.computed(function() {
        evaluator.call(owner).done(result);
    });

    return result;
}

To make it work with modern standard-conforming promise libraries (as well as jQuery’s almost-promises) the done should be changed to then. But also we need to eliminate that ko.computed:

function asyncComputed(evaluator, owner) {
    var result = ko.observable();
    var wrapper = ko.pureComputed(result);

    ko.execute(wrapper, function() {
        evaluator.call(owner).done(result);
    });

    return wrapper;
}

See how it’s done? We dress up the result in pureComputed clothes, and that’s what we return, so our caller will be able to depend on it and so wake it up. And internally, we use that same pureComputed to be the “nanny” of our ko.execute, so it will wake up in sync with the nanny. When we get a result from the evaluator function, we poke it into the result observable.

Note how we obey the rules of ko.execute: we pass it a pureComputed as the first argument, and the second argument is an evaluator function that returns nothing and does not depend on the first argument.

Introducing knockout.clear

These few simple facilities combine to form a framework in which you can use Knockout without ever needing to dispose anything manually. Instead of your observables transitioning from alive to dead (or disposed), which is a one-way street, they are now able to transition between asleep and awake as necessary. When they are asleep, they are potentially garbage-collectable, not kept alive by being a lingering subscriber.

I’ve put them together in a very small library: knockout.clear

Let me know if you find it useful!

Advertisements
Categories: Uncategorized Tags:
  1. johnnyreilly
    March 2, 2015 at 3:37 pm

    Hey Daniel,

    If you haven’t heard it already you might find this interesting: http://www.dotnetrocks.com/default.aspx?ShowNum=1075

    From about 15 minutes in Steve Sanderson (creator of Knockout) talks about memory management and Knockout. He goes into some of the problems faced and how they were solved building the Azure portal (which Steve works on).

    • earwicker
      March 4, 2015 at 11:52 pm

      Thanks! I just listened to that a few weeks ago. It sounded like he described a systematic way of using dispose, which is what I’d been doing, but it’s a pain in the neck if you have bits of view model that sometimes get temporarily unbound and then re-bound to the DOM (e.g. tab pages). You have to explicitly organise a tree of ownership and manage the bounded lifetimes manually. The advantage of the pattern and library I describe here is that it removes the need for dispose altogether. Really it’s all enabled by ko.pureObservable, which is a standard KO feature since 3.2 – I’ve just added some tools to help make it applicable to more (hopefully all) situations.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: