文章出處
文章列表
著作權歸作者所有。
商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
作者:長天之云
鏈接:http://www.zhihu.com/question/19805411/answer/15465427
來源:知乎
商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
作者:長天之云
鏈接:http://www.zhihu.com/question/19805411/answer/15465427
來源:知乎
不推薦用外部變量鎖定或修改按鈕狀態的方式,因為那樣比較難:
我推薦用主動查詢狀態的方式(A、B,jQuery 為例)或工具函數的方式(C、D)來去除重復操作,并提供一些例子作為參考:
A. 獨占型提交
只允許同時存在一次提交操作,并且直到本次提交完成才能進行下一次提交。
B. 貪婪型提交
無限制的提交,但是以最后一次操作為準;亦即需要盡快給出最后一次操作的反饋,而前面的操作結果并不重要。
比如某些應用的條目中,有一些進行類似「喜歡」或「不喜歡」操作的二態按鈕。如果按下后不立即給出反饋,用戶的目光焦點就可能在那個按鈕上停頓許久;如果按下時即時切換按鈕的狀態,再在程序上用 abort 來實現積極的提交,這樣既能提高用戶體驗,還能降低服務器壓力,皆大歡喜。
C. 節制型提交
無論提交如何頻繁,任意兩次有效提交的間隔時間必定會大于或等于某一時間間隔;即以一定頻率提交。
如果客戶發送每隔100毫秒發送過來10次請求,此模塊將只接收其中6個(每個在時間線上距離為150毫秒)進行處理。
這也是解決查詢沖突的一種可選手段,比如以知乎草稿舉例,仔細觀察可以發現:
編輯器的 blur 事件會立即觸發保存;
保存按鈕的 click 事件也會立即觸發保存;
但是存在一種情況會使這兩個事件在數毫秒內連續發生——當焦點在編輯器內部,并且直接去點擊保存按鈕——這時用 throttle 來處理是可行的。
另外還有一些事件處理會很頻繁地使用 throttle,如: resize、scroll、mousemove。
D. 懶惰型提交
任意兩次提交的間隔時間,必須大于一個指定時間,才會促成有效提交;即不給休息不干活。
還是以知乎草稿舉例,當在編輯器內按下 ctrl + s 時,可以手動保存草稿;如果你連按,程序會表示不理解為什么你要連按,只有等你放棄連按,它才會繼續。
============
更多記憶中的例子
方式 C 和 方式 D 有時更加通用,比如這些情況:
而方式 C 甚至可以和方式 B 組合使用,比如自動完成組件(Google 首頁的搜索就是):
----- update 2013-01-08 -----
E. 記憶型
對于同樣的參數,其返回始終結果是恒等的——每次都將返回同一對象。
應用例子有編輯器,如粘貼內容時抓取其中的鏈接信息,memoize 用以保證同樣的鏈接不會抓取兩次。
----- update 2013-03-27 -----
F. 累積型
前幾天處理自動完成事件時得到這個函數,發現也可以用在處理連續事件上,它能夠把連續的多次提交合并為一個提交,比如:
樣例實現:
----- update 2013-04-16 -----
另一種累積是按時間而不是次數,比如應用在行為統計上,可能在瞬間收集到數十上百類似的行為,這時可以用上面 pile 的結構加上 debounce 來防止大批重復請求(但又不丟失任何統計):
G. 采樣型
這是最近重構時聯想到的,一種和上面都不同的去重操作,可以應用在自動加載(timeline)行為控制上:
如果 sample 是固化的選擇函數(n 選 1),它這實際上會這樣工作:
但「自動加載」的應用可能想要的是(兩次自動,一次手動):
對于這種情況,可以定義作為配置的選擇函數來實現控制:
即每個下一次加載完成之后, 每三次有兩次對下一次加載實行自動加載。
- 要考慮并理解 success, complete, error, timeout 這些事件的區別,并注冊正確的事件,一旦失誤,功能將不再可用;
- 不可避免地比普通流程要要多注冊一個 complete 事件;
- 恢復狀態的代碼很容易和不相干的代碼混合在一起;
我推薦用主動查詢狀態的方式(A、B,jQuery 為例)或工具函數的方式(C、D)來去除重復操作,并提供一些例子作為參考:
A. 獨占型提交
只允許同時存在一次提交操作,并且直到本次提交完成才能進行下一次提交。
module.submit = function() {
if (this.promise_.state() === 'pending') {
return
}
return this.promise_ = $.post('/api/save')
}
B. 貪婪型提交
無限制的提交,但是以最后一次操作為準;亦即需要盡快給出最后一次操作的反饋,而前面的操作結果并不重要。
module.submit = function() {
if (this.promise_.state() === 'pending') {
this.promise_.abort()
}
// todo
}
C. 節制型提交
無論提交如何頻繁,任意兩次有效提交的間隔時間必定會大于或等于某一時間間隔;即以一定頻率提交。
module.submit = throttle(150, function() {
// todo
})
這也是解決查詢沖突的一種可選手段,比如以知乎草稿舉例,仔細觀察可以發現:
編輯器的 blur 事件會立即觸發保存;
保存按鈕的 click 事件也會立即觸發保存;
但是存在一種情況會使這兩個事件在數毫秒內連續發生——當焦點在編輯器內部,并且直接去點擊保存按鈕——這時用 throttle 來處理是可行的。
另外還有一些事件處理會很頻繁地使用 throttle,如: resize、scroll、mousemove。
D. 懶惰型提交
任意兩次提交的間隔時間,必須大于一個指定時間,才會促成有效提交;即不給休息不干活。
module.submit = debounce(150, function() {
// todo
})
============
更多記憶中的例子
方式 C 和 方式 D 有時更加通用,比如這些情況:
- 游戲中你撿到一把威力強大的高速武器,為了防止你的子彈在屏幕上打成一條直線,可以 throttle 來控制頻率;
- 在彈幕型游戲里,為了防止你把射擊鍵夾住來進行無腦游戲,可以用 debounce 來控制頻率;
- 在編譯任務里,守護進程監視了某一文件夾里所有的文件(如任一文件的改變都可以觸發重新編譯,一次執行就需要2秒),但某種操作能夠瞬間造成大量文件改變(如 git checkout),這時一個簡單的 debounce 可以使編譯任務只執行一次。
而方式 C 甚至可以和方式 B 組合使用,比如自動完成組件(Google 首頁的搜索就是):
- 當用戶快速輸入文本時(特別是打字能手),可以 throttle keypress 事件處理函數,以指定時間間隔來提取文本域的值,然后立即進行新的查詢;
- 當新的查詢需要發送,但上一個查詢還沒返回結果時,可以 abort 未完成的查詢,并立即發送新查詢;
----- update 2013-01-08 -----
E. 記憶型
var scrape = memoize(function(url) {
return $.post('/scraper', { 'url': url })
})
應用例子有編輯器,如粘貼內容時抓取其中的鏈接信息,memoize 用以保證同樣的鏈接不會抓取兩次。
----- update 2013-03-27 -----
F. 累積型
前幾天處理自動完成事件時得到這個函數,發現也可以用在處理連續事件上,它能夠把連續的多次提交合并為一個提交,比如:
var request = makePile(5, function() {
$.post('/', { list: JSON.stringify([].slice.call(arguments)) })
})
// 連續發送五次
request({a:1}), request({a:2}), request({a:3}), request({a:4}), request({a:5})
/* post =>
list:[{"a":1},{"a":2},{"a":3},{"a":4},{"a":5}]
*/
var makePile = function(count, onfilter, onvalue) {
var values = [], id = function(value) { return value }
return function(value) {
values.push((onvalue || id).apply(this, arguments))
if (values.length === count) {
onfilter.apply(this, values)
values = []
}
}
}
----- update 2013-04-16 -----
另一種累積是按時間而不是次數,比如應用在行為統計上,可能在瞬間收集到數十上百類似的行為,這時可以用上面 pile 的結構加上 debounce 來防止大批重復請求(但又不丟失任何統計):
var trackFactory = function(delay, action) {
var params = [], slice = [].slice
var touch = debounce(delay, function() {
if (params.length) {
action(params)
params = []
}
})
return function() {
params.push(slice.call(arguments))
touch()
}
}
var track = trackFactory(550, function(params) {
// send tracking request
})
G. 采樣型
這是最近重構時聯想到的,一種和上面都不同的去重操作,可以應用在自動加載(timeline)行為控制上:
autoload.listen(feeds, 'next', sample(3, function() {
this.enable()
}))
如果 sample 是固化的選擇函數(n 選 1),它這實際上會這樣工作:
O-O-X-O-O-X
但「自動加載」的應用可能想要的是(兩次自動,一次手動):
X-X-O-X-X-O
對于這種情況,可以定義作為配置的選擇函數來實現控制:
options { sample: (n) => n % 3 !== 0 }
編輯于 2013-04-16 31 條評論 感謝
收藏 • 沒有幫助 •
• 作者保留權利
分享
舉報
我的經驗的做法是,封裝自己的Ajax請求對象.不使用直接使用系統的或者其他第三方的包裝.有了自己的封裝之后,并不是簡單abort就是最好的.我這里會根據實際的情況做處理:1. 所有的Ajax請求都是異步的,我的封裝中做一個100毫秒的setTimeout延時.這樣就可以有效… 顯示全部
我的經驗的做法是,封裝自己的Ajax請求對象.不使用直接使用系統的或者其他第三方的包裝.
有了自己的封裝之后,并不是簡單abort就是最好的.
我這里會根據實際的情況做處理:
1. 所有的Ajax請求都是異步的,我的封裝中做一個100毫秒的setTimeout延時.這樣就可以有效的解決用戶快速重復點擊的問題. 足夠快的時候,前一請求并沒有真正的發出來.就被clearTimeout清除掉了. abort掉Ajax的請求也會有服務端響應,需要消耗資源.
2.如果用戶的操作不并足夠快.比如點了保存按鈕,在服務器沒有返回成功的時候,再次點了保存按鈕.(事實上兩次請求是一樣的.而我們更希望第一次的請求是有效的,第二次的可以不做處理).
在封裝中,我們檢查傳入ajax的請url及參數是否一致,如果一致.則第二次的AJAX并不發出.
3.最后一種情況,兩次相間的請求是不同的請求,比如樓上說快速切換Tab的例子.無法,只能abort掉前一個Ajax請求了.
再補充一點,統一的Ajax封裝有許多特別的好處:
1.可以計數pending中的請求數,顯示當前還有幾個請求進行中(我們原來的系統中有一個這樣的需求,一般系統也會有比如顯示loading的需求...)
2.可以有統一的異常處理,發送的請求失敗了(服務器可以返回統一的格式,客戶端做集中處理,彈出錯框等,或者如firebug那樣,把所有的異常放到一個統一的地方去.或者調試階段增加處理.)
3.如果有大量的小請求,比如取某個表單的下拉列表.一堆下拉框的話.可以在客戶端做請求合并.batching Ajax, 這個是從DWR中獲得的靈感.
有了自己的封裝之后,并不是簡單abort就是最好的.
我這里會根據實際的情況做處理:
1. 所有的Ajax請求都是異步的,我的封裝中做一個100毫秒的setTimeout延時.這樣就可以有效的解決用戶快速重復點擊的問題. 足夠快的時候,前一請求并沒有真正的發出來.就被clearTimeout清除掉了. abort掉Ajax的請求也會有服務端響應,需要消耗資源.
2.如果用戶的操作不并足夠快.比如點了保存按鈕,在服務器沒有返回成功的時候,再次點了保存按鈕.(事實上兩次請求是一樣的.而我們更希望第一次的請求是有效的,第二次的可以不做處理).
在封裝中,我們檢查傳入ajax的請url及參數是否一致,如果一致.則第二次的AJAX并不發出.
3.最后一種情況,兩次相間的請求是不同的請求,比如樓上說快速切換Tab的例子.無法,只能abort掉前一個Ajax請求了.
再補充一點,統一的Ajax封裝有許多特別的好處:
1.可以計數pending中的請求數,顯示當前還有幾個請求進行中(我們原來的系統中有一個這樣的需求,一般系統也會有比如顯示loading的需求...)
2.可以有統一的異常處理,發送的請求失敗了(服務器可以返回統一的格式,客戶端做集中處理,彈出錯框等,或者如firebug那樣,把所有的異常放到一個統一的地方去.或者調試階段增加處理.)
3.如果有大量的小請求,比如取某個表單的下拉列表.一堆下拉框的話.可以在客戶端做請求合并.batching Ajax, 這個是從DWR中獲得的靈感.
如果用jquery的話,jquery的ajax提供ajaxPrefilter方法對ajax請求進行攔截,可以在公用的js里配置上1個公用的ajaxPrefilter。全局維護1個request的列表,在ajaxPrefilter中判斷要發送的請求是否已經存在,如不存在則發送并將發送中的請求存儲到本地,同時覆…
顯示全部
如果用jquery的話,jquery的ajax提供ajaxPrefilter方法對ajax請求進行攔截,可以在公用的js里配置上1個公用的ajaxPrefilter。全局維護1個request的列表,在ajaxPrefilter中判斷要發送的請求是否已經存在,如不存在則發送并將發送中的請求存儲到本地,同時覆蓋掉傳進來的options下的complete屬性,在里面包裹上請求完成后踢出隊列的處理。
其它框架的話用類似的方式處理,基本思想就是對原始提供的全局ajax方法進行簡單包裝下即可。
以上是sample code。
幾個地方都可以定制:
其它框架的話用類似的方式處理,基本思想就是對原始提供的全局ajax方法進行簡單包裝下即可。
// 所有ajax請求的通用前置filter
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
// 不重復發送相同請求
var key = generatePendingRequestKey(options);
if (!pendingRequests[key]) {
storePendingRequest(key, jqXHR);
} else {
// or do other
jqXHR.abort();
}
var complete = options.complete;
options.complete = function(jqXHR, textStatus) {
// clear from pending requests
pendingRequests[jqXHR.pendingRequestKey] = null;
if ($.isFunction(complete)) {
complete.apply(this, arguments);
}
};
});
以上是sample code。
幾個地方都可以定制:
- 如何算同1個請求
- 遇到發送相同請求的具體處理,可以選擇abort之前的,也可以直接abort掉后發的,完全取決于業務需求
知呼上的一篇文章不錯:http://www.zhihu.com/question/19805411
文章列表
全站熱搜
創作者介紹