Custom throttle extender in knockout.js

2019-08-20 09:18发布

问题:

I have an observable that's binded to some input, and sometimes it's value changes too fast, so that the end user does not have time to read it. So I want to restrict the speed of changes in input.

But it's not a throttle, because throttle is a bottle neck, and the throttled observable does not change at all while it changes. I want to have a custom throttle, so that the first change applies immediately, and then it might change only after a delay (and of course, each time after a delay it shows the CURRENT value).

So far I've written the custom restrictSpeedChange extender. Here it is: http://jsfiddle.net/kasheftin/Pn9r8/4/. And it actually works for usual observables.

ko.extenders.restrictChangeSpeed = function(target,timeout) {
    var writeTimeoutInstance = null;
    var currentValue = target();
    var updateValueAgain = false;
    return ko.dependentObservable({
        read: target,
        write: function(value) {
            var updateValue = function(value) {
                target(value);
                if (!writeTimeoutInstance) {
                    writeTimeoutInstance = setTimeout(function() {
                        writeTimeoutInstance = null;
                        if (updateValueAgain) {
                            updateValueAgain = false;
                            updateValue(currentValue);
                        }
                    },timeout);
                }
            }
            currentValue = value;
            if (!writeTimeoutInstance)
                updateValue(currentValue);
            else
                updateValueAgain = true;
        }
    });
}

The problem is I want it to work with computed observables as well. For them throttle extender has throttleEvaluation variable, this variable is used in dependentObservable.js evaluatePossiblyAsync method. But I don't want to change anything in core knockout files.

In my example http://jsfiddle.net/kasheftin/Pn9r8/4/ usual observable variable is restrictChangeSpeedVar1 and it works as expected. Computed variable is restrictChangeSpeedComputedVar1. What should I do to make it work like the first one?

回答1:

A quick thought going with your existing code:

In your extender, you could see if you are dealing with a non-writeable computed observable, then return an observable that has gone through your extender and has subscribed to the computed.

ko.extenders.restrictChangeSpeed = function(target, timeout) {
    var writeTimeoutInstance = null;
    var currentValue = target();
    var updateValueAgain = false;
    var interceptor;

    if (ko.isComputed(target) && !ko.isWriteableObservable(target)) {
        interceptor = ko.observable().extend({ restrictChangeSpeed: timeout });
        target.subscribe(interceptor);
        return interceptor;
    }
....

The key is that you need something to go through your "write" logic. With a normal computed, it would just re-evaluate its "read" logic and update, never going through your "write" code.

Here is a sample: http://jsfiddle.net/rniemeyer/GPbrR/

Also, you had a typo in your fiddle:

self.restrictChangeSpeedComputedVar1 = ko.computed(this.var1).extend({restictChangeSpeed:1000});

You had misspelled your extender name (restict instead of restrict), which made me scratch my head while testing the changes that I added, until I noticed it.

The only interesting thing about my changes is that now someone could potentially write to your computed, but it would always update whenever the underlying computed changed and I don't see why you would intentionally try to write to it.