因為工作的需要,我要在網頁端寫一段腳本,把資料透過網頁批次提交到系統中去。所以我就想到了Greasemonkey插件,於是就開始動手寫,發現問題解決得很順利。但在對腳本進行總結和整理的時候,我習慣性地問了自己一個問題:能不能再簡單一點?
我的答案當然是「能」。
首先回顧我的資料批次提交的需求:我有一批使用者資料要插入到系統中,但是因為系統函式庫表結構不是行列式的,所以無法轉換為sql語句插入。要插入的資料有接近200條,就是傻呵呵地手工輸入到系統,估計也要1天的時間。身為程式設計師,當然不會做這麼傻的事情,我一定要用程式來解決。這個程式的過程耗費了我1天的時間。相比手工錄入,我額外收入是這篇博文,絕對的合算!
程式設計平台選擇沒花時間,直接選定基於Greasemonkey寫自己的腳本,瀏覽器當然是firefox了。腳本的工作過程:
在腳本中預先存放要插入的數據
模擬滑鼠點擊,打開頁面中的輸入窗口
將數據錄入到輸入窗口,並模擬點擊“提交”按鈕,將數據提交到系統中。
依序循環,直到所有資料都處理完畢。
這裡的技術困難在於:
開啟輸入窗口,需要等待不定期的時間,視網路情況而定。
提交資料到後台,需要等待處理完畢之後才可以循環下一個資料。
如果我是菜鳥的話,我當然直接寫一個類似這樣的應用邏輯:
for(var i = 0; i { 3: clickButtonForInputWindow();
waitInputWindow();
Input(dataArray]] ;
clickSubmitButton();
waitInputWindowClose();
}
實際上這樣寫所有瀏覽器都會陷入一片白屏,並在若干分鐘之後提示「沒有回應」而被強行終止掉。原因就是瀏覽器在呼叫javascript的時候,主介面是停止回應的,因為cpu交給js執行了,沒有時間去處理介面訊息。
為了滿足「不鎖死」的要求,我們可以把腳本修改成這樣:
複製程式碼
程式碼如下:
for(var i = 0; i {
setTimeout(clickButtonForInputWindow);
… setTimeout(waitose) ;
}
實際上setTimeout和setInterval是瀏覽器唯一可以支援非同步的操作。如何更優雅地使用這兩個函數來實現非同步操作呢?目前簡單的答案是老趙的Wind.js。雖然我沒有用過這個函數庫,但是光是$await調用,就是符合我一貫對簡潔的要求的。但是對於我這樣的單一檔案的腳本來說,去網上下載一個外部js庫,明顯不如有一段支援非同步操作的程式碼拷貝過來的快和爽。
所以我決定另闢蹊徑,做一個不要編譯而且易用性還可以更能夠Copy&Paste的非同步函數函式庫。
說非同步之前,我們一起回想一下同步運算的幾種結構類型:
順序:就是語句的先後順序執行
判斷:就是判斷語句
循環:嚴格來說應該是跳轉(goto),但大多數現代語言都取消了goto。循環其實應該是複合結構,是if和goto的組合。
非同步操作的困難點在兩個地方:
非同步的判斷:非同步情況下的判斷基本上都是偵測條件十分滿足,然後執行某些動作。
非同步的順序:順序中的每一步操作之後都要交回控制權,等待在下一個時間片中繼續執行下一步。難點是如何保持順序性。尤其在兩個順序動作中間夾雜一個非同步的循環的時候。
非同步的循環:每次循環之後都交回控制權到瀏覽器,如此循環,直到運行結束。
最簡單的實作當然就是非同步循環了,我的實作程式碼如下:複製程式碼
程式碼如下:
function asyncWhile(fn, interval)
{
if( fn == null || (typeof(fn) != "string" && typeof(fn) != "function") )
return;
var wrapper = function()
{
if( (typeof(fn) == "function" ? fn() : eval(fn) ) !== false )
setTimeout(wrapper, interval == null? 1: interval);
}
wrapper(); }
核心內容就是:如果fn函數回傳值不是false,就繼續下一個setTimeout的登記呼叫。
實際上,「等待並執行」邏輯,根本上就是一個非同步循環問題。這種情況的實作方法範例如下:
asyncWhile(function (){
if( xxxCondition == false )
return true; // 表示繼續循環
else
doSomeThing();
return false; // 表示不需要繼續循環了
});
對於非等待並執行的邏輯,簡單一個setTimeout 就可以了。
非同步容易,實現非同步中的順序才叫難度。最早的起因是我要實現3步,但第二部是一個非同步的100多次的循環。也就是說,我要實現的3步驟操作,其實是103次的順序非同步操作。為了一個如何在瀏覽器中實現可回應的等待,找破了腦袋,只找到一個firefox中的實現,還要申請特權呼叫。
最後想出了一個簡單的方法,就是引入了「執行鏈(Execution Chain)」的概念,同一個執行鏈的所有登記函數是順序的,不同執行鏈之間沒有任何關係。另外,不提供互斥(mutex)等概念,如果要同步,請自行在程式碼中檢查。
在同一個執行鏈中,保存一個執行令牌,只有令牌和函數序號匹配,才允許執行,這樣就保證了非同步執行的順序性。
function asyncSeq(uncray, Name,funcray> {
if( typeof(funcArray) == "function" )
return asyncSeq([funcArray], chainName, abortWhenError);
if( funcArray == null || 0 )
return;
if( chainName == null ) chainName = "__default_seq_chain__";
var tInfos = asyncSeq.chainInfos = asyncSeq.chainInfos 4q}; tInfos[chainName] = tInfos[chainName] || {count : 0, currentIndex : -1, abort : false};
for(var i = 0; i for(var i = 0; i {
asyncWhile(function(item, tIndex){
return function(){
if( tInfo.abort )
return false;
if( tInfo.currentIndex else if( tInfo.currentIndex == tIndex )
{
try{
item();
}
catch(e){
if(abortWhenf ) tInfo.abort = true;
}
finally{
tInfo.currentIndex ;
}
}
else
{
if( abortWhenErrort = tInfotWhenError ). true;
}
return false;
};
}(funcArray[i], tInfo.count ));
}
setTimeout(function(){
if( tInfo.count > 0 && tInfo.currentIndex == -1 )
tInfo.currentIndex = 0;
},20); // 為了除錯的原因,加上了延遲啟動
}
由此,一個支援Copy&Paste的非同步js函式庫就完成了。具體的使用範例如下:
複製程式碼 程式碼如下:
function testAsync()
{
asyncSeq([function(){println("aSyncSeq -0 ");}
, function(){println("aSyncSeq -1) ;}
, function(){println("aSyncSeq -2 ");}
, function(){println("aSyncSeq -3 ");}
, function(){println("aSyncSeq -4 ");}
, function(){println("aSyncSeq -5 ");}
, function(){println("aSyncSeq -6 ");}
, function(){ println("aSyncSeq -7 ");}
, function(){println("aSyncSeq -8 ");}
, function(){println("aSyncSeq -9 ");}
, function(){println("aSyncSeq -10 ");}
, function(){println("aSyncSeq -11 ");}
, function(){println("aSyncSeq -12 ");
, function(){println("aSyncSeq -13 ");}
, function(){println("aSyncSeq -14 ");}
, function(){println("aSyncSeq -15 ");}
, function(){println("aSyncSeq -16 ");}
, function(){println("aSyncSeq -17 ");}
, function(){println( "aSyncSeq -18 ");}
, function(){println("aSyncSeq -19 ");}
, function(){println("aSyncSeq -20 ");}
, function( ){println("aSyncSeq -21 ");}
, function(){println("aSyncSeq -22 ");}
, function(){println("aSyncSeq -23 ");}
, function(){println("aSyncSeq -24 ");}
, function(){println("aSyncSeq -25 ");}
, function(){println("aSyncSeq -26 ") ;}
, function(){println("aSyncSeq -27 ");}
, function(){println("aSyncSeq -28 ");}
, function(){println("aSyncSeq -29 ");}
]);
asyncSeq([function(){println("aSyncSeq test-chain -a0 ");}
, function(){println("aSyncSeq test-chain -a1 ");}
}
, function(){println("aSyncSeq test-chain -a2 ");}
, function(){println("aSyncSeq test-chain -a3 ");}
, function(){println(" aSyncSeq 測試鏈-a4 ");}
, function(){println("aSyncSeq test-chain -a5 ");}
, function(){println("aSyncSeq test-chain -a6 ") ; }
, function(){println("aSyncSeq test-chain -a7 ");}
, function(){println("aSyncSeq test-chain -a8 ");}
], "測試鏈」);
asyncSeq([function(){println("aSyncSeq -a0 ");}
, function(){println("aSyncSeq -a1 ");}
, function (){println ("aSyncSeq -a2 ");}
, function(){println("aSyncSeq -a3 ");}
, function(){println("aSyncSeq -a4 ");}
, function (){println("aSyncSeq -a5 ");}
, function(){println("aSyncSeq -a6 ");}
, function(){println("aSyncSeq -a7 " );}
, function(){println("aSyncSeq -a8 ");}
]);
var textArea = null
function println; (print; 🎜>{
if( textArea == null )
{
textArea = document.getElementById("text");
textArea.value = "";
}
textArea.value = textArea.value text "rn";
}
最後,要向大家說聲抱歉,復興程式碼的恐怕朋友要失望了,如果你真的不知道怎麼處理這些多餘的行號,你可以學習一下正則表達式的替換,推薦用UltraEdit。