防止ajax重複發送請求

怎樣防止重複發送 Ajax 請求?
一個簡易的需求,點一個按鈕,則向服務器請求資源,不作處理時,多次點擊後會有很多個請求在等待。粗暴的解決方式是點一次就將按鈕disable掉。請問一下有沒有更好的辦法,比如多點一次後自動down掉前一次請求?補充:不是一次請求,更類似於gmail的全站AJAX,剛用firebug看了一下gmail,發現重複請求時,之前的請求狀態變爲“Aborted”,並且不反回任何數據。請問是如何做到的呢?畢竟一般理解客戶端AJAX發送後是不能終止的。
添加評論  分享
按票數排序
按時間排序
13 個回答


贊同
90
反對,不會顯示你的姓名
長天之雲,head, body { content : '' }
張玥、鄭士龍、朱禁 等人贊同
不推薦用外部變量鎖定或修改按鈕狀態的方式,因爲那樣比較難:
要考慮並理解 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
}
比如某些應用的條目中,有一些進行類似「喜歡」或「不喜歡」操作的二態按鈕。如果按下後不立即給出反饋,用戶的目光焦點就可能在那個按鈕上停頓許久;如果按下時即時切換按鈕的狀態,再在程序上用 abort 來實現積極的提交,這樣既能提高用戶體驗,還能降低服務器壓力,皆大歡喜。


C. 節制型提交
無論提交如何頻繁,任意兩次有效提交的間隔時間必定會大於或等於某一時間間隔;即以一定頻率提交。
module.submit = throttle(150, function() {
  // todo
})
如果客戶發送每隔100毫秒發送過來10次請求,此模塊將只接收其中6個(每個在時間線上距離爲150毫秒)進行處理。
這也是解決查詢衝突的一種可選手段,比如以知乎草稿舉例,仔細觀察可以發現:
編輯器的 blur 事件會立即觸發保存;
保存按鈕的 click 事件也會立即觸發保存;
但是存在一種情況會使這兩個事件在數毫秒內連續發生——當焦點在編輯器內部,並且直接去點擊保存按鈕——這時用 throttle 來處理是可行的。
另外還有一些事件處理會很頻繁地使用 throttle,如: resize、scroll、mousemove。


D. 懶惰型提交
任意兩次提交的間隔時間,必須大於一個指定時間,纔會促成有效提交;即不給休息不幹活。
module.submit = debounce(150, function() {
  // todo
})
還是以知乎草稿舉例,當在編輯器內按下 ctrl + s 時,可以手動保存草稿;如果你連按,程序會表示不理解爲什麼你要連按,只有等你放棄連按,它纔會繼續。


============
更多記憶中的例子


方式 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}]
 */
發佈了13 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章