HTML5實戰之本地存儲(3) - 請求同步
在實際上項目當中,經常需要使用短輪詢(每隔一定時間就向服務器發送一次請求,請求通常會立即返回)和長輪詢(每次請求服務器會Hold一段時間直到有新數據或者超時,客戶端收到數據后會立即進行下一次請求)來從服務器拉取數據,然后動態的更新頁面。隨著功能的增加,一個頁面中往往存在不止一個這樣的輪詢請求,而且在用戶開啟多個Tab頁面時,總得請求數會翻倍。而“請求同步”指的是在以上場景中,即使是開啟多個Tab也只有一個Tab頁面維持輪詢連接,一旦數據返回后,就將數據同步到其他無連接的頁面,最大程度的減少請求。實現的思路也很簡單,先解決由誰來進行請求的問題,之后基于本地存儲將數據進行同步即可。具體的實現則有不少細節需要注意的,以下詳細闡明。
一、判斷是否需要發起請求
首先,短輪詢和長輪詢是有區別的:
短輪詢不會長時間維持一個請求,通常都是請求數據返回后會隔一段時間再次發起請求。因此頁面在判斷自己是否需要發起請求時通常只需要判斷:
1. 數據是否有效
2. 當前是否有其他頁面已經在請求中
3.請求是否已經超時
而長輪詢時,每個請求都會持續比較長的時間,請求一旦返回后會立即再次發送請求。這時判斷是否需要請求的依據是:
1. 當前是否有其他頁面已經在請求中
2. 請求是否已經超時
在長輪詢中由于兩次請求之間通常是不會有間隔的,因此不會考慮數據的有效性問題。
綜合一下,無論長短輪詢,一個頁面需要嘗試進行 請求的條件如下:
1. 當前數據已失效
當數據返回時會在本地存儲中記錄數據的返回時間,這樣就可以計算數據的生存周期進而判斷是否失效;長輪詢時會把數據的有效期設置為0,即始終認為數據是失效的。
2.當前無其他頁面在請求或請求已超時
當某個頁面發起請求時會在本地存儲中標記自己處于請求狀態,請求返回后會再次標記自己狀態為未請求。
另外,當一個請求發送后有可能會因為某些原因超時,例如項目中長輪詢的最大持續時間是50s,考慮各種誤差,如果這個時間大于60s的話則可以視為超時。當本地存儲中即使已經標記了有頁面正在請求,但是請求已超時則也會被視為應當發起請求的一個條件。
計算請求是否超時的方法是在請求發送前在本地存儲中記錄發送時間。
以下是上述條件判斷的具體實現:
/*
檢查數據是否過期
*/
isDataValid: function(){
var st = this.storage,
timestamp = parseInt( st.getItem( this.timestampKey ), 10 );
return (new Date() * 1 - timestamp ) < this.interval;
},
/*
檢查當前是否有進行中的請求
無請求時key值為0
storage中標記為請求中并且liveAge沒有到期
*/
isLive: function(){
var st = this.storage,
state = parseInt( st.getItem( this.stateKey ), 10 ),
timestamp = parseInt( st.getItem( this.timestampKey ), 10 );
var isLive = !!state && (new Date() * 1 - timestamp ) < this.liveAge;
return isLive;
}
此外,以上的判斷過程每個頁面都會使用一個Timer來定期的Check。
二、確定請求權
解決了判斷是否需要請求的問題后,下一步就是需要解決由誰來發請求的問題了。第一步中描述一個頁面判斷需要發起請求后是嘗試進行 請求,因此此時可能同時有N個頁面都發現需要進行請求,此時就需要從N個中選擇一個。
實現的方式依然是借助本地存儲解決的,方案如下:
1. 產生一個隨機數A
2. 將A寫入本地存儲中,key為B
3. 40ms之后讀取key為B的本地存儲數據,如果其值為A則表明自己獲得請求權,否則繼續嘗試連接
實際上就是利用瀏覽器本身對于寫存儲數據的并行控制來實現請求權的確定。
具體實現代碼如下:
/*
防止多個頁面進行同一個操作,通過寫storage來爭奪機會
*/
pk: function(win,lose){
var random = Math.random(),
ins = this,
st = this.storage;
var compare = function() {
var val = st.getItem( ins.pkKey );
if ( val == random ) {
win && win();
} else {
lose && lose();
}
};
st.setItem( this.pkKey, random);
setTimeout( compare, 40);
}
三、同步請求數據
以上兩個問題解決后,數據同步的問題就更容易解決了:
1. 所有頁面使用Storage的onstorage接口監聽具體的key – KEY_A,一旦KEY_A發生變化就調用響應的Callback
2. 當請求的數據返回時,將數據更新到KEY_A中
/*
監聽數據變更
*/
monitor: function(){
var st = this.storage,
ins = this;
if( ins.callback ){
st.onstorage( this.dataKey, function(data){
//只有當數據被其他頁面修改后才觸發
if( !ins.localRequest ){
ins.callback( data );
}
});
}
}
四、使用
var p = new Poller({
url: 'gettime.php',
interval: 3000,
liveAge: 10000,
callback: function( data ){
if( p.localRequest ){
data += ' 數據來自當前頁的Ajax請求';
}
else{
data += ' 數據來自其他頁的數據同步';
}
$('#content').html( data );
}
});
p.start();
此外,如果瀏覽器不支持本地存儲或者手動設置了不使用本地存儲則每個頁面都會發送請求。
具體的演示實例請參考http://www.原網站已經失效/pages/html5/storage/sync/request_sync.html