異步javascript的原理和實現(xiàn)技巧介紹
更新時間:2012年11月08日 09:25:42 作者:
因為工作的需要,我要在網(wǎng)頁端編寫一段腳本,把數(shù)據(jù)通過網(wǎng)頁批量提交到系統(tǒng)中去。所以我就想到了Greasemonkey插件,于是就開始動手寫,發(fā)現(xiàn)問題解決得很順利
因為工作的需要,我要在網(wǎng)頁端編寫一段腳本,把數(shù)據(jù)通過網(wǎng)頁批量提交到系統(tǒng)中去。所以我就想到了Greasemonkey插件,于是就開始動手寫,發(fā)現(xiàn)問題解決得很順利。但是在對腳本進行總結和整理的時候,我習慣性地問了自己一個問題:能不能再簡單點?
我的答案當然是“能”。
首先回顧我的數(shù)據(jù)批量提交的需求:我有一批用戶數(shù)據(jù)要插入到系統(tǒng)中,但是因為系統(tǒng)庫表結構不是行列式的,所以無法轉(zhuǎn)化為sql語句插入。要插入的數(shù)據(jù)有接近200條,就是傻呵呵地手工錄入到系統(tǒng),估計也要1天的時間。作為程序員,當然不會干這么傻的事情,我一定要用程序來解決。這個編程的過程耗費了我1天的時間。相比手工錄入,我額外收入是這篇博文,絕對的合算!
編程平臺選擇沒花費時間,直接選定基于Greasemonkey寫自己的腳本,瀏覽器當然是firefox了。腳本的工作過程:
在腳本中預先存放要插入的數(shù)據(jù)
模擬鼠標點擊,打開頁面中的輸入窗口
將數(shù)據(jù)錄入到輸入窗口,并模擬點擊“提交”按鈕,將數(shù)據(jù)提交到系統(tǒng)中。
依次循環(huán),直到所有數(shù)據(jù)都處理完畢。
這里的技術難點在于:
打開輸入窗口,需要等待不定期的時間,視網(wǎng)絡情況而定。
提交數(shù)據(jù)到后臺,需要等待處理完畢之后才可以循環(huán)下一個數(shù)據(jù)。
如果我是菜鳥的話,我當然直接寫一個類似這樣的應用邏輯:
for(var i = 0; i < dataArray.length; ++i)
{ 3: clickButtonForInputWindow();
waitInputWindow();
enterInputData(dataArray[i]);
clickSubmitButton();
waitInputWindowClose();
}
實際上這樣寫所有瀏覽器都會陷入一片白屏,并在若干分鐘之后提示“沒有響應”而被強行終止掉。原因就是瀏覽器在調(diào)用javascript的時候,主界面是停止響應的,因為cpu交給js執(zhí)行了,沒有時間去處理界面消息。
為了滿足“不鎖死”的要求,我們可以把腳本修改成這樣:
for(var i = 0; i < dataArray.length; ++i)
{
setTimeout(clickButtonForInputWindow);
…
setTimeout(waitInputWindowClose);
}
實際上setTimeout和setInterval是瀏覽器唯一可以支持異步的操作。如何更優(yōu)雅地使用這兩個函數(shù)來實現(xiàn)異步操作呢?目前簡單的答案是老趙的Wind.js。雖然我沒有用過這個函數(shù)庫,但是光是$await調(diào)用,就是符合我一貫對簡潔的要求的。但是對于我這樣的單個文件的腳本來說,去網(wǎng)上下載一個外部js庫,明顯不如有一段支持異步操作的代碼拷貝過來的快和爽。
所以我決定另辟蹊徑,做一個不要編譯而且易用性還可以更能夠Copy&Paste的異步函數(shù)庫。
說異步之前,我們一起回憶一下同步操作的幾種結構類型:
順序:就是語句的先后順序執(zhí)行
判斷:就是判斷語句
循環(huán):嚴格來說應該是跳轉(zhuǎn)(goto),但大多數(shù)現(xiàn)代語言都取消了goto。循環(huán)其實應該是復合結構,是if和goto的組合體。
異步操作的難點在兩個地方:
異步的判斷:異步情況下的判斷基本都是檢測條件十分滿足,然后執(zhí)行某些動作。
異步的順序:順序中的每一步操作之后都要交回控制權,等待在下一個時間片中繼續(xù)執(zhí)行下一步。難點是如何保持順序性。尤其在兩個順序動作中間夾雜一個異步的循環(huán)的時候。
異步的循環(huán):每次循環(huán)之后都交回控制權到瀏覽器,如此循環(huán),直到運行結束。
最簡單的實現(xiàn)當然就是異步循環(huán)了,我的實現(xiàn)代碼如下:
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();
}
核心內(nèi)容就是:如果fn函數(shù)返回值不是false,就繼續(xù)下一個setTimeout的登記調(diào)用。
實際上,“等待并執(zhí)行”邏輯,根本上就是一個異步循環(huán)問題。這種情況的實現(xiàn)方法示例如下:
asyncWhile(function(){
if( xxxCondition == false )
return true; // 表示繼續(xù)循環(huán)
else
doSomeThing();
return false; // 表示不需要繼續(xù)循環(huán)了
});
對于非等待并執(zhí)行的邏輯,簡單一個 setTimeout 就可以了。
異步容易,實現(xiàn)異步中的順序才叫難度呢。最早的起因是我要實現(xiàn)3步,但是第二部是一個異步的100多次的循環(huán)。也就是說,我要實現(xiàn)的3步操作,其實是103次的順序異步操作。為了一個如何在瀏覽器中實現(xiàn)可響應的等待,找破了腦袋,只找到一個firefox中的實現(xiàn),還要申請?zhí)貦嗾{(diào)用。
最后想出了一個簡單的方法,就是引入了“執(zhí)行鏈(Execution Chain)”的概念,同一個執(zhí)行鏈的所有登記函數(shù)是順序的,不同執(zhí)行鏈之間沒有任何關系。另外,不提供互斥(mutex)等概念,如果要同步,自行在代碼中檢查。
在同一個執(zhí)行鏈中,保存一個執(zhí)行令牌,只有令牌和函數(shù)序號匹配,才允許執(zhí)行,這樣就保證了異步執(zhí)行的順序性。
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); // 為了調(diào)試的原因,加了延遲啟動
}
由此,一個支持Copy&Paste的異步js函數(shù)庫就完成了。具體的使用例子如下:
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";
}
最后,要向大家說一聲抱歉,很多只想拿代碼的朋友恐怕要失望了,如果你真的不知道怎么處理這些多余的行號,你可以學習一下正則表達式的替換,推薦用UltraEdit。
我的答案當然是“能”。
首先回顧我的數(shù)據(jù)批量提交的需求:我有一批用戶數(shù)據(jù)要插入到系統(tǒng)中,但是因為系統(tǒng)庫表結構不是行列式的,所以無法轉(zhuǎn)化為sql語句插入。要插入的數(shù)據(jù)有接近200條,就是傻呵呵地手工錄入到系統(tǒng),估計也要1天的時間。作為程序員,當然不會干這么傻的事情,我一定要用程序來解決。這個編程的過程耗費了我1天的時間。相比手工錄入,我額外收入是這篇博文,絕對的合算!
編程平臺選擇沒花費時間,直接選定基于Greasemonkey寫自己的腳本,瀏覽器當然是firefox了。腳本的工作過程:
在腳本中預先存放要插入的數(shù)據(jù)
模擬鼠標點擊,打開頁面中的輸入窗口
將數(shù)據(jù)錄入到輸入窗口,并模擬點擊“提交”按鈕,將數(shù)據(jù)提交到系統(tǒng)中。
依次循環(huán),直到所有數(shù)據(jù)都處理完畢。
這里的技術難點在于:
打開輸入窗口,需要等待不定期的時間,視網(wǎng)絡情況而定。
提交數(shù)據(jù)到后臺,需要等待處理完畢之后才可以循環(huán)下一個數(shù)據(jù)。
如果我是菜鳥的話,我當然直接寫一個類似這樣的應用邏輯:
復制代碼 代碼如下:
for(var i = 0; i < dataArray.length; ++i)
{ 3: clickButtonForInputWindow();
waitInputWindow();
enterInputData(dataArray[i]);
clickSubmitButton();
waitInputWindowClose();
}
實際上這樣寫所有瀏覽器都會陷入一片白屏,并在若干分鐘之后提示“沒有響應”而被強行終止掉。原因就是瀏覽器在調(diào)用javascript的時候,主界面是停止響應的,因為cpu交給js執(zhí)行了,沒有時間去處理界面消息。
為了滿足“不鎖死”的要求,我們可以把腳本修改成這樣:
復制代碼 代碼如下:
for(var i = 0; i < dataArray.length; ++i)
{
setTimeout(clickButtonForInputWindow);
…
setTimeout(waitInputWindowClose);
}
實際上setTimeout和setInterval是瀏覽器唯一可以支持異步的操作。如何更優(yōu)雅地使用這兩個函數(shù)來實現(xiàn)異步操作呢?目前簡單的答案是老趙的Wind.js。雖然我沒有用過這個函數(shù)庫,但是光是$await調(diào)用,就是符合我一貫對簡潔的要求的。但是對于我這樣的單個文件的腳本來說,去網(wǎng)上下載一個外部js庫,明顯不如有一段支持異步操作的代碼拷貝過來的快和爽。
所以我決定另辟蹊徑,做一個不要編譯而且易用性還可以更能夠Copy&Paste的異步函數(shù)庫。
說異步之前,我們一起回憶一下同步操作的幾種結構類型:
順序:就是語句的先后順序執(zhí)行
判斷:就是判斷語句
循環(huán):嚴格來說應該是跳轉(zhuǎn)(goto),但大多數(shù)現(xiàn)代語言都取消了goto。循環(huán)其實應該是復合結構,是if和goto的組合體。
異步操作的難點在兩個地方:
異步的判斷:異步情況下的判斷基本都是檢測條件十分滿足,然后執(zhí)行某些動作。
異步的順序:順序中的每一步操作之后都要交回控制權,等待在下一個時間片中繼續(xù)執(zhí)行下一步。難點是如何保持順序性。尤其在兩個順序動作中間夾雜一個異步的循環(huán)的時候。
異步的循環(huán):每次循環(huán)之后都交回控制權到瀏覽器,如此循環(huán),直到運行結束。
最簡單的實現(xiàn)當然就是異步循環(huán)了,我的實現(xiàn)代碼如下:
復制代碼 代碼如下:
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();
}
核心內(nèi)容就是:如果fn函數(shù)返回值不是false,就繼續(xù)下一個setTimeout的登記調(diào)用。
實際上,“等待并執(zhí)行”邏輯,根本上就是一個異步循環(huán)問題。這種情況的實現(xiàn)方法示例如下:
復制代碼 代碼如下:
asyncWhile(function(){
if( xxxCondition == false )
return true; // 表示繼續(xù)循環(huán)
else
doSomeThing();
return false; // 表示不需要繼續(xù)循環(huán)了
});
對于非等待并執(zhí)行的邏輯,簡單一個 setTimeout 就可以了。
異步容易,實現(xiàn)異步中的順序才叫難度呢。最早的起因是我要實現(xiàn)3步,但是第二部是一個異步的100多次的循環(huán)。也就是說,我要實現(xiàn)的3步操作,其實是103次的順序異步操作。為了一個如何在瀏覽器中實現(xiàn)可響應的等待,找破了腦袋,只找到一個firefox中的實現(xiàn),還要申請?zhí)貦嗾{(diào)用。
最后想出了一個簡單的方法,就是引入了“執(zhí)行鏈(Execution Chain)”的概念,同一個執(zhí)行鏈的所有登記函數(shù)是順序的,不同執(zhí)行鏈之間沒有任何關系。另外,不提供互斥(mutex)等概念,如果要同步,自行在代碼中檢查。
在同一個執(zhí)行鏈中,保存一個執(zhí)行令牌,只有令牌和函數(shù)序號匹配,才允許執(zhí)行,這樣就保證了異步執(zhí)行的順序性。
復制代碼 代碼如下:
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); // 為了調(diào)試的原因,加了延遲啟動
}
由此,一個支持Copy&Paste的異步js函數(shù)庫就完成了。具體的使用例子如下:
復制代碼 代碼如下:
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";
}
最后,要向大家說一聲抱歉,很多只想拿代碼的朋友恐怕要失望了,如果你真的不知道怎么處理這些多余的行號,你可以學習一下正則表達式的替換,推薦用UltraEdit。
相關文章
js在數(shù)組中刪除重復的元素自保留一個(兩種實現(xiàn)思路)
遍歷要刪除的數(shù)組arr, 把元素分別放入另一個數(shù)組tmp中,在判斷該元素在arr中不存在才允許放入tmp中,具體實現(xiàn)如下,需要的朋友可以看看2014-08-08詳解JS中continue關鍵字和break關鍵字的區(qū)別
在javascript中continue的作用是退出當前次循環(huán),break的作用則是一旦當前循環(huán)有break那么直接退出整個循環(huán)。本文將通過一些示例為大家詳細講講二者的區(qū)別,感興趣的可以了解一下2022-08-08JS小功能(checkbox實現(xiàn)全選和全取消)實例代碼
這篇文章主要介紹了checkbox實現(xiàn)全選和全取消實例代碼,有需要的朋友可以參考一下2013-11-11js通過元素class名字獲取元素集合的具體實現(xiàn)
獲取元素集合的方法有很多,接下來為大家介紹喜愛使用js通過元素class名字獲取元素集合的方法2014-01-01JS簡單限制textarea內(nèi)輸入字符數(shù)量的方法
這篇文章主要介紹了JS簡單限制textarea內(nèi)輸入字符數(shù)量的方法,涉及JavaScript響應鼠標及鍵盤事件處理textarea輸入框字符的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10微信小程序?qū)崿F(xiàn)多個按鈕的顏色狀態(tài)轉(zhuǎn)換
這篇文章主要為大家詳細介紹了微信小程序?qū)崿F(xiàn)多個按鈕的顏色狀態(tài)轉(zhuǎn)換,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02