Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout subscription insight: detecting if disposed

I have a subscription management in my ko model, which saves any subscription by intercepting them and saving reference to them.

I then dispose() them, but I need to understand sometimes if a sub is already disposed. Is the member Gb, a boolean of the subscription object, an active-inactive flag? I see that sometimes when I dispose a subscription its Gb becomes false, sometimes not. Do I have to interpret this as a dispose fail?

And once I dispose a subscription, the subscription object, is ready to be garbage collected?

EDIT: I just need to understand if a subscription is already disposed, as said in the title. I obtain the subscription in the only way I know, saving a reference to it when declared:

var mySub = myObservable.subscribe(function(){/*do something*/});

Some time after I need a way to determine if mySub is already disposed.

function isDisposed(mySubscription) {
    // if is disposed return true else return false
}

I need to perform some logic based on this, not only dispose it if it is not disposed already (or I could simply call again the dispose method). Is it possible to determine subsbcription disposal status?

like image 798
Nillus Avatar asked Feb 10 '23 20:02

Nillus


1 Answers

About manual subscriptions

Knockout source is compiled with Google Closure Compiler so only properties and methods explicetly exported in source appear in compiled library code.

With that said a subscription has a "private" property isDisposed, but it is not exported. Thus the only API exported for subscription is dispose.

Glimps of source code - ko.subscription (knockout-3.1.0.debug):

ko.subscription = function (target, callback, disposeCallback) {
    this.target = target;
    this.callback = callback;
    this.disposeCallback = disposeCallback;
    this.isDisposed = false;
    ko.exportProperty(this, 'dispose', this.dispose);
};
ko.subscription.prototype.dispose = function () {
    this.isDisposed = true;
    this.disposeCallback();
};

About memory leaks and computeds

1) Just an interesting fact about computeds - consider the following computed

var myObservable1 = ko.observable(true);
var myObservable2 = ko.observable('foo');

var myComputed = ko.computed(function () {
    if (myObservable1()) {
        return myObservable2() + 'bar';
    }
});

In this example myComputed has 2 dependecies. But if we were to assing false to myObservable1 myComputed would reevaluate and after reevalution it would only have 1 dependency and subscription to myObservable2 would be disposed.

Why it is disposed:

The explanation lies in how computed is evaluated - it registers dependencies the following way - if any observable is read (meaning code like myObservable1()) during evaluation - computed recieves a callback with this observable, checks its id and stores it in a new dependency array. Once evaluation is complete - old dependency array is disposed. In our example when myObservable1 is set to false myObservable2 is never read - since we never enter if block. So it is not a new dependecy and old dependency is disposed.

2) Another interesting fact. Consider snippet:

(function () {
    var myObservable = ko.observable(0);
    var myModel = {
        myComputed: ko.computed(function () {
            console.log(myObservable());
        })
    };
    myModel = undefined;
    myObservable(42); // Outputs 42
})();

The computed is not collected by garbage collector because infact the reference to it exists inside it's dependency.

Glimps of source code - ko.computed (knockout-3.1.0.debug):

function addSubscriptionToDependency(subscribable, id) {
    if (!_subscriptionsToDependencies[id]) {
        _subscriptionsToDependencies[id] = subscribable.subscribe(evaluatePossiblyAsync);
        ++_dependenciesCount;
    }
}
...
function evaluatePossiblyAsync() {
    var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
    if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
        clearTimeout(evaluationTimeoutInstance);
        evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
    } else if (dependentObservable._evalRateLimited) {
        dependentObservable._evalRateLimited();
    } else {
        evaluateImmediate();
    }
}

Reference to dependentObservable is preserved because reference to evaluatePossiblyAsync is preserved (Closures, JS and all that Jazz).

Whoa, that is all I had to say. Hope some thought come to mind.

like image 100
Olga Avatar answered Feb 20 '23 05:02

Olga