Navigation Menu

Skip to content

Commit

Permalink
#387 - prevent recursive evaluation of computed observables
Browse files Browse the repository at this point in the history
  • Loading branch information
mbest committed Mar 15, 2012
1 parent bc98894 commit c0a3200
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 1 deletion.
11 changes: 11 additions & 0 deletions spec/dependentObservableBehaviors.js
Expand Up @@ -226,5 +226,16 @@ describe('Dependent Observable', {
value_of(timesEvaluated).should_be(0);
value_of(instance()).should_be(123);
value_of(timesEvaluated).should_be(1);
},

'Should prevent recursive calling of read function': function() {
var observable = ko.observable(0),
computed = ko.dependentObservable(function() {
// this both reads and writes to the observable
// will result in errors like "Maximum call stack size exceeded" (chrome)
// or "Out of stack space" (IE) or "too much recursion" (Firefox) if recursion
// isn't prevented
observable(observable() + 1);
});
}
})
9 changes: 8 additions & 1 deletion src/subscribables/dependentObservable.js
@@ -1,6 +1,7 @@
ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
var _latestValue,
_hasBeenEvaluated = false,
_isBeingEvaluated = false,
readFunction = evaluatorFunctionOrOptions;

if (readFunction && typeof readFunction == "object") {
Expand Down Expand Up @@ -58,6 +59,9 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
}

function evaluateImmediate() {
if (_isBeingEvaluated)
return;

// Don't dispose on first evaluation, because the "disposeWhen" callback might
// e.g., dispose when the associated DOM element isn't in the doc, and it's not
// going to be in the doc until *after* the first evaluation
Expand All @@ -66,6 +70,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return;
}

_isBeingEvaluated = true;
try {
// Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
// Then, during evaluation, we cross off any that are in fact still being used.
Expand All @@ -86,6 +91,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
if (disposalCandidates[i])
_subscriptionsToDependencies.splice(i, 1)[0].dispose();
}
_hasBeenEvaluated = true;

dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
_latestValue = newValue;
Expand All @@ -95,7 +101,8 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
}

dependentObservable["notifySubscribers"](_latestValue);
_hasBeenEvaluated = true;
_isBeingEvaluated = false;

}

function dependentObservable() {
Expand Down

0 comments on commit c0a3200

Please sign in to comment.