【问题标题】:How can I force a throttled observable to update immediately?如何强制限制的 observable 立即更新?
【发布时间】:2013-11-11 18:53:56
【问题描述】:

我通过将其可见性绑定到名为 waiting 的 observable 来在我的 UI 上显示或隐藏“正在加载”指示器,其定义如下:

// Viewmodel
var outstandingRequests = ko.observable(0);

// true if any requests are outstanding
var waiting = ko.computed(function() {
    return outstandingRequests() > 0;
}.extend({ throttle: 500 });

// These are called when AJAX requests begin or end
function ajaxBegin() {
    outstandingRequests(++outstandingRequests());
}
function ajaxEnd() {
    outstandingRequests(--outstandingRequests());
}

<!-- View -->
<div data-bind="visible: waiting">Please wait, loading...</div>

我正在限制waiting observable,因为我不希望出现加载消息,除非请求花费很长时间(在这种情况下 > 500 毫秒),以提高应用程序的感知速度。问题在于,一旦长时间运行的请求完成,加载指示器不会消失,直到经过额外的 500 毫秒。相反,当最后一个未完成的请求完成时,我希望 waiting立即转为 false

我第一次尝试修复涉及使用valueHasMutated(),但更新仍然延迟。

function ajaxEnd() {
    outstandingRequests(--outstandingRequests());
    // If that was the last request, we want the loading widget to disappear NOW.
    outstandingRequests.valueHasMutated(); // Nope, 'waiting' still 500ms to update :(
}

如何绕过油门扩展并强制waiting 立即更新?

【问题讨论】:

    标签: knockout.js computed-observable


    【解决方案1】:

    当你扩展一个 observable 时,扩展器通常会用另一个具有所需行为的 observable 包装这个 observable。您可以保留对原始 observable 的引用,这将允许您直接写入所有数据,同时正常公开您的 observable 的节流版本。

    例如,

    var myObservable = ko.observable('foo');
    var myThrottledObservable = myObservable.extend({ throttle: 500 });
    myThrottledObservable('bar'); // delayed
    myObservable('baz'); // immediate
    

    在您的特定用例中,不要限制waiting observable,而是限制outstandingRequests observable 并使用waiting 中的限制值。

    var outstandingRequests = ko.observable(0);
    
    // throttled requests for the waiting observable
    var throttledOutstandingRequests = outstandingRequests.extend({ throttle: 500 });
    
    // true if any requests are outstanding
    var waiting = ko.computed(function() {
        return throttledOutstandingRequests() > 0;
    };
    
    // These are called when AJAX requests begin or end
    function ajaxBegin() {
        outstandingRequests(++outstandingRequests());
    }
    function ajaxEnd() {
        outstandingRequests(--outstandingRequests());
    }
    

    写入您的outstandingRequests observable 会立即发生,但您的waiting observable 将被有效地限制。


    另外,我认为更简洁的解决方案是重新实现 throttled 扩展器以添加立即更新的能力。

    ko.extenders['throttleEx'] = function(target, timeout) {
        // Throttling means two things:
    
        // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
        //     notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
        target['throttleEvaluation'] = timeout;
    
        // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
        //     so the target cannot change value synchronously or faster than a certain rate
        var writeTimeoutInstance = null;
        var throttled = ko.dependentObservable({
            'read': target,
            'write': function(value) {
                clearTimeout(writeTimeoutInstance);
                writeTimeoutInstance = setTimeout(function() {
                    target(value);
                }, timeout);
            }
        });
    
        // add function to set the value directly
        throttled['immediate'] = function(value) {
            target(value);
        };
    
        return throttled;
    };
    

    然后使用它:

    var waiting = ko.computed(function() {
        return outstandingRequests() > 0;
    }.extend({ throttleEx: 500 });
    
    // These are called when AJAX requests begin or end
    function ajaxBegin() {
        outstandingRequests.immediate(++outstandingRequests());
    }
    function ajaxEnd() {
        outstandingRequests.immediate(--outstandingRequests());
    }
    

    【讨论】:

    • 啊,所以throttle 只是一个包装,我明白了。但是如何将我的 UI 绑定到多个可观察对象以获得所需的行为?
    • 为了最简洁的实现,我可能会编写自己的 throttle 扩展器实现来添加该功能并在您计算的 observable 上使用它。或者,在您的特定情况下,您可以限制 outstandingRequests observable 而不是 waiting 并在我的示例中使用该方法。
    • 在考虑了您的情况后,我认为这不是理想的解决方案。尽管延迟,任何outstandingRequests的更改都会触发对waiting的更新最终因此快速更改不会将它们全部合并为一个更改。我将不得不重新考虑那个。
    • 与其说我希望将它们全部集中到一个更改中,不如说我只希望 sometimes 的 observable。换句话说,我需要一个throttleUnlessNewValueIsZero 扩展器...
    • 转念一想,我错了。回顾throttled 的实现后,对值的快速更改仍然只会发出一次更新。它的行为仍然几乎相同。
    【解决方案2】:

    您真正想要的是延迟来自waiting observable 的通知,当它变为true 时。这可以通过拦截observable的notifySubscribers函数来完成:

    var originalNotifySubscribers = this.isWaiting.notifySubscribers,
        timeoutInstance;
    this.isWaiting.notifySubscribers = function(value, event) {
        clearTimeout(timeoutInstance);
        if ((event === 'change' || event === undefined) && value) {
            timeoutInstance = setTimeout(function() {
                originalNotifySubscribers.call(this, value, event);
            }.bind(this), 500);
        } else {
            originalNotifySubscribers.call(this, value, event);
        }
    };
    

    jsFiddle:http://jsfiddle.net/mbest/Pk6mH/

    编辑: 我只是为您的特定情况想到了另一个可能更好的解决方案。由于waiting observable 仅依赖于另一个 observable,因此您可以创建手动订阅来更新 waiting observable:

    var timeoutInstance;
    this.isLoading.subscribe(function(value) {
        clearTimeout(timeoutInstance);
        if (value) {
            timeoutInstance = setTimeout(function() {
                this.isWaiting(true);
            }.bind(this), 500);
        } else {
            this.isWaiting(false);
        }
    }, this);
    

    jsFiddle:http://jsfiddle.net/mbest/wCJHT/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-17
      • 2015-08-30
      • 1970-01-01
      • 2019-06-10
      相关资源
      最近更新 更多