Unable to process binding error when async observable disposes mid-update
0 reputation · 23 Jul 2020, 14:09 UTC
0 reputation · 23 Jul 2020, 14:09 UTC
When a custom binding handler initiates asynchronous work (e.g., a throttled observable or a deferred extender), the developer must ensure that any pending notifications or timers are cancelled if the bound DOM node is removed by Knockout.
The framework offers ko.utils.domNodeDisposal.addDisposeCallback for cleanup, but it does not automatically dispose ko.computed or pureComputed instances created inside the handler, nor does it cancel pending rateLimit or deferred notifications.
This leaves an open decision about who should own the disposal of such async resources and what the recommended pattern is for guaranteeing cancellation without relying on manual try/catch or memory‑leak prone code.
Should Knockout automatically dispose computeds created in binding handlers when the node is removed? If not, what is the prescribed way to bind the lifetime of throttled/deferred observables to the DOM node lifecycle? How can developers reliably cancel pending deferred extender notifications associated with a binding?
34775 reputation · 23 Jul 2020, 15:51 UTC
Knockout does not automatically dispose of ko.computed, ko.pureComputed, or timers created inside a custom binding handler. While the framework manages the subscriptions it creates to trigger the update function, any resources instantiated within your init or update logic are owned by the handler and must be manually cleaned up.
To bind the lifetime of async resources to the DOM node, use ko.utils.domNodeDisposal.addDisposeCallback. This ensures that when Knockout removes the element (via if, foreach, or template bindings), the associated resources are terminated.
Implementation Steps:
init function to ensure it is only created once per node lifecycle..dispose() on those resources.rateLimit or custom setTimeout calls, ensure the disposal of the parent computed clears the internal timer.ko.bindingHandlers.asyncExample = {
init: function(element, valueAccessor) {
var value = valueAccessor();
// Create a rate-limited computed
var internalComputed = ko.computed(function() {
return ko.unwrap(value);
}, null, { rateLimit: 500 });
// Bind lifetime to the DOM node
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
internalComputed.dispose();
});
},
update: function(element, valueAccessor) {
// Update logic here
}
};
Pending notifications from deferred extenders or rate-limited observables are tied to the subscription or computed that triggers them. By disposing of the ko.computed that wraps the deferred logic, you effectively cancel the pending notification queue for that specific node instance.
innerHTML = '' or other external DOM manipulation, the disposal callback will not fire.To verify the fix, implement a console.log inside the dispose callback and use a ko.observable tied to an if binding to toggle the node. Confirm that the log fires and that no throttled updates execute after the node is removed from the DOM.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.