非同步javascript的原理和實現技巧介紹

來源:互聯網
上載者:User

因為工作的需要,我要在網頁端編寫一段指令碼,把資料通過網頁批量提交到系統中去。所以我就想到了Greasemonkey外掛程式,於是就開始動手寫,發現問題解決得很順利。但是在對指令碼進行總結和整理的時候,我習慣性地問了自己一個問題:能不能再簡單點?
我的答案當然是“能”。

首先回顧我的資料批量提交的需求:我有一批使用者資料要插入到系統中,但是因為系統庫表結構不是行列式的,所以無法轉化為sql語句插入。要插入的資料有接近200條,就是傻呵呵地手工錄入到系統,估計也要1天的時間。作為程式員,當然不會幹這麼傻的事情,我一定要用程式來解決。這個編程的過程耗費了我1天的時間。相比手工錄入,我額外收入是這篇博文,絕對的合算!

編程平台選擇沒花費時間,直接選定基於Greasemonkey寫自己的指令碼,瀏覽器當然是firefox了。指令碼的工作過程:
在指令碼中預先存放要插入的資料
類比滑鼠點擊,開啟頁面中的輸入視窗
將資料錄入到輸入視窗,並類比點擊“提交”按鈕,將資料提交到系統中。
依次迴圈,直到所有資料都處理完畢。

這裡的技術痛點在於:
開啟輸入視窗,需要等待不週期性時間,視網路情況而定。
提交資料到後台,需要等待處理完畢之後才可以迴圈下一個資料。
如果我是菜鳥的話,我當然直接寫一個類似這樣的應用邏輯: 複製代碼 代碼如下:for(var i = 0; i < dataArray.length; ++i)
{ 3: clickButtonForInputWindow();
waitInputWindow();
enterInputData(dataArray[i]);
clickSubmitButton();
waitInputWindowClose();
}

實際上這樣寫所有瀏覽器都會陷入一片白屏,並在若干分鐘之後提示“沒有響應”而被強行終止掉。原因就是瀏覽器在調用javascript的時候,主介面是停止回應的,因為cpu交給js執行了,沒有時間去處理介面訊息。

為了滿足“不鎖死”的要求,我們可以把指令碼修改成這樣: 複製代碼 代碼如下:for(var i = 0; i < dataArray.length; ++i)
{
setTimeout(clickButtonForInputWindow);

setTimeout(waitInputWindowClose);
}

實際上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(funcArray, chainName, abortWhenError)
{
if( typeof(funcArray) == "function" )
return asyncSeq([funcArray], chainName, abortWhenError);

if( funcArray == null || funcArray.length == 0 )
return;

if( chainName == null ) chainName = "__default_seq_chain__";
var tInfos = asyncSeq.chainInfos = asyncSeq.chainInfos || {};
var tInfo = tInfos[chainName] = tInfos[chainName] || {count : 0, currentIndex : -1, abort : false};

for(var i = 0; i < funcArray.length; ++i)
{
asyncWhile(function(item, tIndex){
return function(){
if( tInfo.abort )
return false;
if( tInfo.currentIndex < tIndex )
return true;
else if( tInfo.currentIndex == tIndex )
{
try{
item();
}
catch(e){
if( abortWhenError ) tInfo.abort = true;
}
finally{
tInfo.currentIndex ++;
}
}
else
{
if( abortWhenError ) tInfo.abort = 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 test-chain -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 ");}
], "test-chain");

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(text)
{
if( textArea == null )
{
textArea = document.getElementById("text");
textArea.value = "";
}

textArea.value = textArea.value + text + "\r\n";
}

最後,要向大家說一聲抱歉,很多隻想拿代碼的朋友恐怕要失望了,如果你真的不知道怎麼處理這些多餘的行號,你可以學習一下Regex的替換,推薦用UltraEdit。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.