Skip to content

Commit

Permalink
Merge pull request #2174 from knockout/2174-minimize-deferred-compute…
Browse files Browse the repository at this point in the history
…d-eval

deferedUpdate causes depended observable chains to reevaluate every time
  • Loading branch information
mbest committed Feb 10, 2017
2 parents 87e8a59 + f5df3c2 commit 1145f4d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 12 deletions.
20 changes: 20 additions & 0 deletions spec/asyncBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,26 @@ describe('Deferred', function() {
expect(notifySpy.argsForCall).toEqual([['i(x,h(cx,g(ex,fx),d(bx,cx)),bx,fx)']]); // only one evaluation and notification
});

it('Should minimize evaluation when dependent computed doesn\'t actually change', function() {
// From https://github.com/knockout/knockout/issues/2174
this.restoreAfter(ko.options, 'deferUpdates');
ko.options.deferUpdates = true;

var source = ko.observable({ key: 'value' }),
c1 = ko.computed(function () {
return source()['key'];
}),
countEval = 0,
c2 = ko.computed(function () {
countEval++;
return c1();
});

source({ key: 'value' });
jasmine.Clock.tick(1);
expect(countEval).toEqual(1);
});

it('Should ignore recursive dirty events', function() {
// From https://github.com/knockout/knockout/issues/1943
this.restoreAfter(ko.options, 'deferUpdates');
Expand Down
36 changes: 25 additions & 11 deletions src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
var state = {
latestValue: undefined,
isStale: true,
isDirty: true,
isBeingEvaluated: false,
suppressDisposalUntilDisposeWhenReturnsFalse: false,
isDisposed: false,
Expand Down Expand Up @@ -45,7 +46,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
} else {
// Reading the value
ko.dependencyDetection.registerDependency(computedObservable);
if (state.isStale || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
if (state.isDirty || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
computedObservable.evaluateImmediate();
}
return state.latestValue;
Expand Down Expand Up @@ -166,16 +167,19 @@ var computedFn = {
markDirty: function () {
// Process "dirty" events if we can handle delayed notifications
if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
this._evalDelayed();
this._evalDelayed(false /*isChange*/);
}
},
isActive: function () {
return this[computedState].isStale || this[computedState].dependenciesCount > 0;
var state = this[computedState];
return state.isDirty || state.dependenciesCount > 0;
},
respondToChange: function () {
// Ignore "change" events if we've already scheduled a delayed notification
if (!this._notificationIsPending) {
this.evaluatePossiblyAsync();
} else if (this[computedState].isDirty) {
this[computedState].isStale = true;
}
},
subscribeToDependency: function (target) {
Expand All @@ -202,7 +206,7 @@ var computedFn = {
computedObservable.evaluateImmediate(true /*notifyChange*/);
}, throttleEvaluationTimeout);
} else if (computedObservable._evalDelayed) {
computedObservable._evalDelayed();
computedObservable._evalDelayed(true /*isChange*/);
} else {
computedObservable.evaluateImmediate(true /*notifyChange*/);
}
Expand Down Expand Up @@ -322,29 +326,39 @@ var computedFn = {
ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
}

state.isStale = false;
state.isStale = state.isDirty = false;
}
},
peek: function () {
// Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
var state = this[computedState];
if ((state.isStale && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
if ((state.isDirty && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
this.evaluateImmediate();
}
return state.latestValue;
},
limit: function (limitFunction) {
// Override the limit function with one that delays evaluation as well
ko.subscribable['fn'].limit.call(this, limitFunction);
this._evalDelayed = function () {
this._evalIfChanged = function () {
if (this[computedState].isStale) {
this.evaluateImmediate();
}
return this[computedState].latestValue;
};
this._evalDelayed = function (isChange) {
this._limitBeforeChange(this[computedState].latestValue);

this[computedState].isStale = true; // Mark as dirty
// Mark as dirty
this[computedState].isDirty = true;
if (isChange) {
this[computedState].isStale = true;
}

// Pass the observable to the "limit" code, which will access it when
// Pass the observable to the "limit" code, which will evaluate it when
// it's time to do the notification.
this._limitChange(this);
}
};
},
dispose: function () {
var state = this[computedState];
Expand All @@ -361,6 +375,7 @@ var computedFn = {
state.dependenciesCount = 0;
state.isDisposed = true;
state.isStale = false;
state.isDirty = false;
state.isSleeping = false;
state.disposeWhenNodeIsRemoved = undefined;
state.disposeWhen = undefined;
Expand All @@ -381,7 +396,6 @@ var pureComputedOverrides = {
if (state.isStale || computedObservable.haveDependenciesChanged()) {
state.dependencyTracking = null;
state.dependenciesCount = 0;
state.isStale = true;
if (computedObservable.evaluateImmediate()) {
computedObservable.updateVersion();
}
Expand Down
2 changes: 1 addition & 1 deletion src/subscribables/subscribable.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ var ko_subscribable_fn = {
// If an observable provided a reference to itself, access it to get the latest value.
// This allows computed observables to delay calculating their value until needed.
if (selfIsObservable && pendingValue === self) {
pendingValue = self();
pendingValue = self._evalIfChanged ? self._evalIfChanged() : self();
}
ignoreBeforeChange = false;
if (self.isDifferent(previousValue, pendingValue)) {
Expand Down

0 comments on commit 1145f4d

Please sign in to comment.