速率限制可观察通知
原文:Rate-limiting observable notifications
中英文名词对照:
- 可观察--observable
- 计算可观察--computed observable
- 速率限制--rate-limit
一般地,一个observable被改变后立即通知它的订阅者,以致任何计算可观察(computed observables)或依赖该可观察的绑定被同步更新。但是速率限制(rateLimit)扩展器促使一个可观察压制和延迟改变通知一段指定的时间。一个速率限制可观察因此异步更新依赖。
速率限制扩展器能被应用到任意类型的可观察,包括可观察数组(observable arrays)和计算可观察(computed observables)。速率限制主要的用例是:
- 在某个延迟后响应
- 组合多个改变到一个单一的更新
应用速率限制扩展器
速率限制支持两个参数格式:// Shorthand: Specify just a timeout in milliseconds
someObservableOrComputed.extend({ rateLimit: 500 });
// Longhand: Specify timeout and/or method
someObservableOrComputed.extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" } });
当通知触发时,上述方法可选控制如下,接收下面的值:
1、 notifyAtFixedRate-- 假如没有其他限定的缺省值。这时通知在从第一次改变到可观察期间(或者初始时或者从先前通知开始)发生。
2、 notifyWhenChangesStop--在指定时期后 可观察没有改变时发生。每次 可观察改变,计时器被重置,因此,假如该 可观察持续不断地改变,比超时期还频繁时,通知不发生。
例1:基础
考虑下面代码中的可观察:
var name = ko.observable('Bert');
var upperCaseName = ko.computed(function() {
return name().toUpperCase();
});
正常地,假如你象下面这样改变名字:
name('The New Bert');
那么upperCaseName将被立即重计算,在你的下一行代码运行之前。但是假如你使用
rateLimit来代替
name定义,见下面的代码:
var name = ko.observable('Bert').extend({ rateLimit: 500 });
那么当
name改变时,
upperCaseName将不被立即重新计算,而是在通知它的新值到
upperCaseName之前,
name将等500毫秒(半秒钟),而后重新计算它的值。不管
name在500ms期间改变多少次,
upperCaseName将仅仅使用最近的值更新一次。
例子2:当用户停止打字时做一些事情
在这个在线例子中,有一个 instantaneousValue可观察,当你按下一个键时会立即反应。这个被包裹在一个 delayedValue 计算可观察配置里面,仅仅当停止400毫秒后通知改变,使用 notifyWhenChangesStop速率限制方法。
试试这个( 译者注:这里该例子不可运行,仅做展示):
Type stuff here:
Current delayed value: sdfssdsdfssdfdsffffff
Stuff you have typed:
- sdfssd
- sdfssdsdfs
- sdfssdsdfssdfdsffffff
源代码:View
<p>Type stuff here: <input data-bind='value: instantaneousValue,
valueUpdate: ["input", "afterkeydown"]' /></p>
<p>Current delayed value: <b data-bind='text: delayedValue'> </b></p>
<div data-bind="visible: loggedValues().length > 0">
<h3>Stuff you have typed:</h3>
<ul data-bind="foreach: loggedValues">
<li data-bind="text: $data"></li>
</ul>
</div>
源代码:View model
function AppViewModel() {
this.instantaneousValue = ko.observable();
this.delayedValue = ko.pureComputed(this.instantaneousValue)
.extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 400 } });
// Keep a log of the throttled values
this.loggedValues = ko.observableArray([]);
this.delayedValue.subscribe(function (val) {
if (val !== '')
this.loggedValues.push(val);
}, this);
}
ko.applyBindings(new AppViewModel());
说明(译者注):
假设timeout设置时间为t,则:
- method: "notifyWhenChangesStop"的作用是,当定时器还未到期时,键入事件将定时器重置清零,如果经过t时刻仍然没有键入,则触发通知事件;
- 若不使用 method: "notifyWhenChangesStop",则定时器不清零,每到t时刻,就触发通知事件,因此不超过t时刻,必然触发一次通知。
例子3:避免多重Ajax请求
下面的模型展示了你能作为一个页表(paged grid)绘制的数据:function GridViewModel() {
this.pageSize = ko.observable(20);
this.pageIndex = ko.observable(1);
this.currentPageData = ko.observableArray();
// Query /Some/Json/Service whenever pageIndex or pageSize changes,
// and use the results to update currentPageData
ko.computed(function() {
var params = { page: this.pageIndex(), size: this.pageSize() };
$.getJSON('/Some/Json/Service', params, this.currentPageData);
}, this);
}
因为该
计算可观察求
pageIndex和
pageSize值, 它变得依赖它们两个。因此,当一个
GridViewModel首先被实例化,并且无论什么时候
pageIndex或
pageSize属性在后来被改变时,这个代码将使用
jQuery's $.getJSON function 来重新加载
currentPageData。
这是非常简单和简洁的(增加更多可观察查询参数无论什么时候改变也触发一个自动刷新也没有问题),但是有一个潜在的效率问题,假定你增加下面的函数到 GridViewModel改变 pageIndex和 pageSize:
this.setPageSize = function(newPageSize) {
// Whenever you change the page size, we always reset the page index to 1
this.pageSize(newPageSize);
this.pageIndex(1);
}
这样做问题是将引发两个Ajax请求:第一个当你更新
pageSize时开始,第二个当你更新pageIndex时立即开始。这是一个带宽和服务器资源的浪费,一个不可预测的竞争条件的根源。
当你应用到 计算可观察时, rateLimit扩展器也将避免额外的计算函数求值。使用一个短的速率限制期(例如0毫秒)确保任何对依赖同步改变的序列将触发仅仅一次你的计算可观察的求值。例如:
ko.computed(function() {
// This evaluation logic is exactly the same as before
var params = { page: this.pageIndex(), size: this.pageSize() };
$.getJSON('/Some/Json/Service', params, this.currentPageData);
}, this).extend({ rateLimit: 0 });
现在你能改变pageIndex和pageSize无论多少次,这个Ajax都将仅仅调用一次,在你发布你的线程到JavaScript后台运行时。
计算可观察的特别条件对一个计算可观察来说,当一个计算可观察的依赖改变而不是值改变时,速率限制被触发。计算可观察直到它的值被真正改变才重新求值——在这个改变通知发生期后,或者当这个计算可观察值被直接访问。假如你需要访问这个计算的最近的求值,你可以使用peek方法这样做。
强迫速率限制可观察总是通知订阅者当任何 可观察值是原生类型(number,string,boolean 或null)时,仅仅当它被设置为一个真正的不同于以前的值时, 可观察的依赖才被缺省通知。因此,在这个时期结束时,原生值 速率限制 可观察通知仅仅当它们的值是真正不同,换句话说,假如一个原生值速率限制可观察被改变到一个新值,然后在这个时期结束又改回原始值,通知不发生。
假如你想确保订阅者总是被通知更新,即使它的值是一样的,你应该使用除了 rateLimit之外的 notifiy扩展器:
myViewModel.fullName = ko.computed(function() {
return myViewModel.firstName() + " " + myViewModel.lastName();
}).extend({ notify: 'always', rateLimit: 500 });
与throttle(节流)扩展器的比较
假如你想从已经废弃的 throttle扩展器移植代码,你应该注意接下的方法, rateLimit扩展器是不同于 throttle扩展器的。当使用 rateLimit:
1、写到可观察是不延迟的;可观察的值能被正确更新。对可写的计算可观察,这意味着写函数总是被正确运行。
2、所有 change通知被延迟,包括当手工调用 valueHasMuted。这意味着你不能使用 valueHasMutated来强迫一个速率限制可观察去通知一个没有改变的值。
3、缺省速率限制方法是不同于 throttle算法的。使用notifyWhenchangesStop方法匹配throttle行为。
4、速率限制计算可观察求值不是速率限制;假如你读它的值,它将重新求值。