Knockout computed observables and DOM bindings: handling stale cache after async dependency updates
0 reputation · 23 Aug 2023, 15:01 UTC
0 reputation · 23 Aug 2023, 15:01 UTC
Goal: Verify that a Knockout computed observable reflects the latest value of its dependencies when those dependencies are updated asynchronously without triggering the observable's change notification.
Constraint: Knockout's computed observables cache their result until a tracked dependency emits a change; the deferredUpdates extender and ko.pureComputed can mitigate stale‑cache behavior but only if subscriptions are correctly disposed. An open design question remains about who bears responsibility for clearing the cache when a dependency is no longer subscribed.
Questions: Should Knockout automatically purge a computed's cache when a dependency is disposed? Does enabling deferredUpdates guarantee that asynchronous updates trigger recomputation? Is explicit subscription disposal the only reliable way to prevent stale data in computed observables?
33775 reputation · 23 Aug 2023, 17:51 UTC
No. Knockout does not automatically purge a computed’s cached value merely because one of its dependencies was disposed. deferredUpdates does not guarantee recomputation for an asynchronous update; it only changes when a valid change notification is processed. Explicit disposal is the reliable way to stop a computed from serving future values after its lifetime ends, but it is not a substitute for making the async dependency emit a change while the computed is still active.
Assuming Knockout 3.5.x/3.6.x, the confirmed model is that a computed caches its last evaluated result until a tracked dependency notifies subscribers. If an async callback mutates a plain object property, a non-observable field, or a value inside an observable without calling the observable setter, no dependency change event is emitted. The cache therefore remains valid from Knockout’s perspective, even though your application data changed.
If the dependency is an observable and the async code calls source(newValue), then deferUpdates should schedule the computed to re-evaluate after the change notification. The likely stale-cache cases are silent mutation, a disposed dependency while the computed remains alive, or a computed that has been disposed but is still being read.
Disposing the computed, or the subscription that keeps it awake, removes its dependency subscriptions and stops future recomputation and notification. If a dependency is disposed first, a still-live computed will not receive further notifications and can keep returning its last cached value. For DOM bindings, dispose the computed with the DOM node or binding context so the consumer cannot outlive its dependencies.
Run this against your bundled Knockout build because cache internals are version-sensitive:
const source = ko.observable('initial');
const live = ko.pureComputed(() => source().length)
.extend({ deferUpdates: true });
// Notifying async update: should recompute after the change event.
setTimeout(() => source('updated'), 0);
// Silent async update: no observable setter, so no cache invalidation.
const silent = { value: 'other' };
const stale = ko.pureComputed(() => silent.value.length);
// Lifecycle cleanup: stop tracking when the view/binding is removed.
// live.dispose();
ko.pureComputed plus deferUpdates when you want batching, not as a cache-clearing mechanism.Is the async dependency updated through a Knockout observable setter, or is it a plain object property mutated inside the callback? If it is plain, the fix is notification, not disposal timing.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.