简单场景
我有一个列表,我使用箭头键(向上,向下)实现浏览,并且在每次更改当前列表项时,数据库对象通过AJAX加载。
甜。
问题
当用户非常快速地浏览列表时,我不希望每个请求都被取消。但当然,原始请求应该立即生效。
我的想法是使用变量作为延迟设置超时,并在项目的初始加载后,增加该变量。
这样可行,但是当用户停止浏览一小段时间但又继续浏览时,我仍然不希望每个请求都被取消。
所以我想,每次浏览事件都必须合理地增加延迟变量,直到达到阈值。
这种有机方法可以成功地减少不必要的物品装载量。
我的解决方案
我走了很远。这段代码(下面的解释)将完成这项工作,只有一个主要罪魁祸首:
第一次浏览完成然后停止后,延迟将自动保持在(第二步)最小值150ms。
当然我试图修复此问题,但正如您将看到的,这是一个有趣但可能相当常见的逻辑问题 - 我认为我的整体方法是错误的。
但我不知道怎么做。大脑无法计算。电脑说没有。
代码
您可以筛选我的示例或go here for a fully functional simulator in jsFiddle。
如果您选择jsFiddle:
单击按钮,立即显示项目加载。现在稍等一下,再次单击该按钮,初始加载将被延迟。如果您持续快速按下按钮,只有在点击完成后才会显示项目加载。
代码示例
我们在对象文字中,只是你知道。
_clickTimer: false, // holds what setTimeout() returns
_timerInc: 0, // the your timer delay are belong to us
/**
* Function is triggered whenever the user hits an arrow key
* itemRef is the passed list item object (table row, in this case)
*/
triggerItemClick: function(itemRef){
var that=this;
var itemId=$(itemRef).data('id'); // Get the item id
if(this._clickTimer){ // If a timeout is waiting
clearTimeout(this._clickTimer); // we clear it
this._itemClickTimer=false; // and reset the variable to false
/**
* Note that we only arrive here after the first call
* because this._clickTimer will be false on first run
*/
if(this._timerInc == 0){ // If our timer is zero
this._timerInc = 150; // we set it to 150
} else { // otherwise
if(this._timerInc <= 350) // we check if it is lower than 350 (this is our threshold)
this._timerInc += 15; // and if so, we increase in steps of 15
}
}
/**
* Regardless of any timing issues, we always want the list
* to respond to browsing (even if we're not loading an item.
*/
this.toggleListItem(itemId);
/**
* Here we now set the timeout and assign it to this._clickTimer
*/
this._clickTimer=setTimeout(function(){
// we now perform the actual loading of the item
that.selectItem(itemId);
// and we reset our delay to zero
that._timerInc=0;
}, this._timerInc); // we use the delay for setTimeout()
}
解释
首次通话时:_clickTimer
为false
,_timerInc
为0
,因此第一次通话会导致0
延迟setTimeout()
}}和_clickTimer
将被设置。该项目将立即加载。
第二次通话 - 鉴于我们的超时仍在等待触发,_clickTimer
被清除,延迟设置为150
如果0
或增加15
低于350
(阈值)。
如果你继续浏览,这很有用。计时器增加,只有在您停止浏览一段时间后才会触发加载。
停止后 但,下次再次继续时,_clickTimer
将不为假(因为setTimeout()
会为其指定一个计数器) ,反过来_timerInc
将立即设置为150
。因此,第一次浏览将导致在加载任何内容之前延迟150ms。
叫我疯狂或挑剔,但目标是不要有这种延迟。
当然你会说:简单,在_clickTimer
闭包结束时将setTimeout()
设置为false,这样一旦浏览完成并且项目被加载就会重置。太好了,但这会导致延迟永远不会超过0ms。仔细想想,你会看到。
我希望这是正确解释的,并且某人的大脑比我的更有能力找到解决方案。
答案 0 :(得分:2)
可能使用Promises以非常复杂的方式执行此操作。由于这主要是糖衣,但我认为必须有可能直接解决这个问题,我想我做了。
Updated fiddle。我在文本中添加了延迟,因此我更容易调试内容,并且还进行了一些小的整理,但我的实际更改非常小。详情如下。
你的评论接近尾声是我的第一个直觉:
当然你会说:简单,在结束时将
_clickTimer
设置为falsesetTimeout()
关闭,因此一旦完成浏览就会重置 项目被加载。很好,但是这会导致延迟永远不会超过0毫秒。
实际上,这会使延迟永远不会超过0,因为我们无法快速点击(或在实际应用中快速浏览)。但是......如果我们只在延迟不是0时重置怎么办?因此,如果超时消失,但仅在0毫秒后消失,我们就会记得超时。如果它晚于此时间,则必须在浏览中实际停顿。这可以通过在超时回调中添加几行来轻松实现,如下所示。
this._clickTimer = setTimeout(function() {
// we now perform the actual loading of the item
that.selectItem();
// and we reset our delay to zero
if (that._timerInc > 0) {
that._clickTimer = false;
}
that._timerInc = 0;
}, this._timerInc); // we use the delay for setTimeout()
它似乎完全按照您的要求工作,但现在,延迟将是0ms,然后是150ms,然后是0ms,等等,如果您在点击之间等待足够长的时间。这可以通过添加额外的超时来解决,以防延迟 0ms仍将重置延迟。每当触发器发生时(单击演示,在应用程序中浏览),此超时将被取消。
我相信这一起使一切按照你想要的方式运作。为了完整起见,我还在上面提到了上面提到的小提琴。
var _simulator = {
_clickTimer: false, // holds what setTimeout() returns
_cancelClickTimer: false,
_timerInc: 0, // the your timer delay are belong to us
/**
* Function is triggered whenever the user hits an arrow key
* itemRef is the passed list item object (table row, in this case)
*/
triggerItemClick: function() {
var that = this;
// always cancel resetting the timing, it can never hurt
clearTimeout(that._cancelClickTimer);
that._cancelClickTimer = false;
if (this._clickTimer) { // If a timeout is waiting
clearTimeout(this._clickTimer); // we clear it
this._clickTimer = false; // and reset the variable to false
/**
* Note that we only arrive here after the first call
* because this._clickTimer will be false on first run
*/
if (this._timerInc == 0) { // If our timer is zero
this._timerInc = 150; // we set it to 150
} else { // otherwise
if (this._timerInc <= 350) // we check if it is lower than 350 (this is our threshold)
this._timerInc += 15; // and if so, we increase in steps of 15
}
}
/**
* Regardless of any timing issues, we always want the list
* to respond to browsing (even if we're not loading an item.
*/
this.toggleListItem();
/**
* Here we now set the timeout and assign it to this._clickTimer
*/
this._clickTimer = setTimeout(function() {
// we now perform the actual loading of the item
that.selectItem();
// and we reset our delay to zero
if (that._timerInc > 0) {
that._clickTimer = false;
} else {
that._cancelClickTimer = setTimeout(function() {
that._clickTimer = false;
}, 150);
}
that._timerInc = 0;
}, this._timerInc); // we use the delay for setTimeout()
},
/** the following functions are irrelevant for the problemsolving above **/
toggleListItem: function() {
$('#status').prepend($('<div />').text('You toggled a list item ... in ' + this._timerInc + ' ms'));
},
selectItem: function(id) {
$('#loader').show();
setTimeout(function() {
$('#loader').hide();
}, 800);
}
};
$('#clickZone').on('click', function() {
_simulator.triggerItemClick();
});
#clickZone {
background: #369;
color: #fff;
width: 420px;
height: 80px;
text-align: center;
line-height: 80px;
cursor: pointer;
-ms-user-select: none;
-moz-user-select: -moz-none;
-webkit-user-select: none;
user-select: none;
font-family: Arial;
}
#status {
line-height: 20px;
margin-top: 10px;
font-family: Arial;
font-size: 12px;
background: #936;
color: #fff;
padding: 7px 10px;
}
#status > div {
padding: 2px 0 4px;
border-bottom: 1px dashed #ddd;
}
#status > div:last-child {
border-bottom: 0;
}
#loader,
#notice {
display: none;
margin-top: 10px;
width: 320px;
padding: 10px 15px;
background: #ddd;
font-family: Arial;
font-size: 11px;
text-align: center;
}
#notice {
background: lightblue;
font-size: 14px;
color: #333;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="clickZone">
CLICK ME TO SIMULATE LIST BROWSING
</div>
<div id="loader">
✓ Browsing ended, loading item!
</div>
<div id="status">
<div>
Waiting for something to happen ...
</div>
</div>
答案 1 :(得分:0)
我们假设您希望每个请求之间至少有100毫秒,但如果最后一个请求超过100毫秒,则应立即获取数据。
以下应该做的技巧(伪代码):
首先,创建以下变量(需要从以下函数访问的变量)
delayLoad // (boolean) - initial false
currentItem // initial 1
lastItemFetched // initial 1
在第一次页面加载时,应显示第1项(&#39;项目&#39;表示每页一个大项目,或整页行 - 如果项目应该是20行页面上的一行一些额外代码将需要跟踪需要获取哪些行 - 但显然,当用户在计时器(延迟)运行时一直按下向下按钮时,至少内部位置应该改变,以给予更快的感觉滚动)。
OnArrowDown:
currentItem++ // and check boundaries
getData()
OnArrowUp:
currentItem-- // and check boundaries
getData()
getData()
if not delayLoad
getRealData()
// else do nothing
getRealData()
delayLoad = true
lastItemFetched = currentItem // currentItem could change while
// data is being fetched
get itemdata for lastItemFetched from server
on receive
update data on page for lastItemFetched
set 100ms timer
onTimer
if currentItem != lastItemFetched
getRealData()
else
delayLoad = false
如果您确实希望延迟滚动,如果用户继续快速滚动,则可以执行以下操作:
delayLoad // (boolean) - initial false
currentItem // initial 1
lastItemFetched // initial 1
changeCount // initial 0
OnArrowDown:
currentItem++ // and check boundaries
getData()
OnArrowUp:
currentItem-- // and check boundaries
getData()
getData()
changeCount++
if not delayLoad
getRealData()
// else do nothing
getRealData()
delayLoad = true
lastItemFetched = currentItem // currentItem could change while
// data is being fetched
get itemdata for lastItemFetched from server
on receive
update data on page for lastItemFetched
set timer to 100 + changeCount * 5 // or some other number
// and maybe set a max value for the total
changeCount = 0
onTimer
if currentItem != lastItemFetched
getRealData()
else
delayLoad = false
为了加快滚动速度 - 假设有一百万个项目且用户一直按住向下箭头,你可以这样做:
OnArrowDown:
currentItem++ // and check boundaries
if changeCount > some_value // user keeps holding down the button
currentItem += changeCount * some_factor // check boundaries
getData()
但是你必须在getRealData()
里面做一些额外的事情,changeCount
重置为0。
这可以与实时更新页面上的currentItem
号码结合使用。
或者,您可以一次获取多个项目并将它们存储在本地缓存中。
答案 2 :(得分:0)
我认为你的问题是,在第一次按键后你无法检测到第二次按键。延迟模式0,150,165,... 350ms是不现实的,因为实际请求需要实际的时间(而不是0ms)。
在一定时间范围内检测按键是一种可能的解决方案。如果在100ms内检测到第二次按键,则从0到150ms延迟。
_clickTimer: false,
_timerInc: 0,
// When was the last trigger action (ms)
_lastTriggerTime: 0,
// Time span after first trigger event, during which the _timerInc
// is advanced.
_initRestartInterval: 100,
// Time span after last trigger event, during which the _timerInc
// is advanced.
_currentRestartInterval: 100,
triggerItemClick: function(itemRef){
var that=this;
var itemId=$(itemRef).data('id');
if(this._clickTimer){
clearTimeout(this._clickTimer);
this._clickTimer = false;
}
var _triggerTime = new Date().getTime();
var _elapsed = _triggerTime - this._lastTriggerTime;
this._lastTriggerTime = _triggerTime;
if (_elapsed > this._currentRestartInterval) {
this._timerInc = 0;
this._currentRestartInterval = this._initRestartInterval;
} else {
if(this._timerInc == 0){
this._timerInc = 150;
} else {
if(this._timerInc <= 350)
this._timerInc += 15;
}
this._currentRestartInterval = this._timerInc;
}
this.toggleListItem(itemId);
this._clickTimer=setTimeout(function(){
// we now perform the actual loading of the item
// :ws: And here is the problem with a simulation that
// does not take any relevant time at all.
// If _clickTimer is set to false, before the "loading"
// has taken a noticable amount of time, the early reset
// problem arises and _timerInc is always 0.
that.selectItem(itemId);
// In an asynchronous environment, the following should
// take place in the result handler.
if (that._timerInc > 0) {
// next trigger event will reset _timerInc to 0
that._lastTriggerTime = 0;
}
}, this._timerInc);
}
这是一个版本,可以正确模拟数据请求。它的行为与第一次点击的时间测量版本完全相同:但是,对于进一步的点击,它的行为会有所不同,因为每个延迟都会增加100毫秒的请求时间。这使得延迟有效地为100,250,265,...... 450ms。
_clickTimer: false,
_timerInc: 0,
// Is there still a request for data?
_pendingRequest: false,
triggerItemClick: function(itemRef){
var that=this;
var itemId=$(itemRef).data('id');
var tooFast = this._clickTimer || this._pendingRequest;
if(this._clickTimer){
clearTimeout(this._clickTimer);
this._clickTimer = false;
}
// asynchronous AJAX request simulated with a timer
if(this._pendingRequest){
clearTimeout(this._pendingRequest);
this._pendingRequest = false;
}
if (! tooFast) {
this._timerInc = 0;
} else {
if(this._timerInc == 0){
this._timerInc = 150;
} else {
if(this._timerInc <= 350)
this._timerInc += 15;
}
}
this.toggleListItem(itemId);
this._clickTimer=setTimeout(function(){
// we now perform the actual loading of the item
// :ws: assuming it will take 100ms
that._pendingRequest = setTimeout(function(){
that.selectItem(itemId);
that._pendingRequest = false;
}, 100);
that._clickTimer = false;
}, this._timerInc);
}