JS中的sleep 、順序執行
function sleep(msec) { var k = function_continuation; setTimeout(function() { resume k <- mesc; }, msec); suspend; }
這個語法更嚇人了,而且還是java裡不被推薦使用的執行緒方法名。坦白說我傾向於 Narrative JS。
同Narrative JS一樣,jwacs也需要預編譯,預編譯器是用 LISP 語言編寫。目前也是 Alpha 的版本。兩者的更多介紹和比較可以參閱 SitePoint 上的新文章: Eliminating async Javascript callbacks by preprocessing
編寫複雜的JavaScript指令碼時,有時會有需求希望指令碼能停滯指定的一段時間,類似於 java 中的 Thread.sleep 或者 sh 指令碼中的 sleep 命令所實現的效果。
眾所周知,JavaScript 並沒有提供類似於 Java 的執行緒控制的功能, 雖然有 setTimeout 和 setInterval 兩個方法可以做一些定時執行控制,但並不能滿足所有的要求。一直以來,都有很多人問如何在JavaScript中實現 sleep/pause/wait ,也確實有些很蹩腳的解決方案:
最簡單也最糟糕的方法就是寫一個迴圈,程式碼可能如下:
function sleep(numberMillis) { var now = new Date(); var exitTime = now.getTime() + numberMillis; while (true) { now = new Date(); if (now.getTime() > exitTime) return; } }
如上的程式碼其實並沒有讓指令碼直譯器sleep下來,而且有讓CPU迅速上到高負荷的附作用。瀏覽器甚至會在該段時間內處於假死狀態。
其二有聰明人利用IE特殊的對話方塊實現來曲徑通幽,程式碼可能如下:
function sleep(timeout) { window.showModalDialog("javascript:document.writeln('<script>window.setTimeout(function () { window.close(); }, " + timeout + ");<//script>');"); }
window.alert("before sleep ..."); sleep(2000); window.alert("after sleep ...");
缺點不用多說,只有IE支援(IE7因為安全限制也而不能達到目的)。
除上之外,還有利用Applet或者呼叫Windows Script Host的WScript.Sleep()等等鬼點子,這些都是萬不得已的權宜之計。
終於有了更聰明的人,開發出了也許是最佳的方案,先看程式碼:
function sleep(millis) { var notifier = NjsRuntime.createNotifier(); setTimeout(notifier, millis); notifier.wait->(); }
沒錯,看到 ->() 這樣的語法,就象剛看到Prototype的 $() 函式一樣讓我驚為天人。不過直接在瀏覽器中這段指令碼是會報告語法錯誤的。實際上它們需要經過預編譯成客戶端瀏覽器認可的JavaScript。編譯後的指令碼如下:
function sleep(millis){var njf1 = njen(this,arguments,"millis");nj:while(1) {try{switch(njf1.cp) { case 0:njf1._notifier=NjsRuntime.createNotifier();setTimeout(njf1._notifier,njf1._millis);njf1.cp = 1;njf1._notifier.wait(njf1);return;case 1:break nj; }} catch(ex) { if(!njf1.except(ex,1)) return; }} njf1.pf();}
我看不懂,也不想去看懂了。這些工作全部會由 Narrative JavaScript ———— 一個提供非同步阻塞功能的JS擴充套件幫我們實現。我們只需要編寫之前那個怪異的 ->() 語法, 然後通過後臺預先靜態編譯或者前臺動態編譯後執行就可以實現 sleep 的效果。
Narrative JavaScript 宣稱可以讓你從頭昏眼花的回撥函式中解脫出來,編寫清晰的Long Running Tasks。目前還是 alpha 的版本,在 Example 頁面上有一個移動的按鈕的範例。首頁上也提供了原始碼下載。以我薄弱的基礎知識,我只能勉強的看出程式碼中模擬了狀態機的實現,希望有精通演算法的朋友能為我們解析。
最後,還是我一直以來的觀點: 除非很必要,否則請保持JavaScript的簡單。在JavaScript 能提供原生的執行緒支援之前,或許我們可以改變設計以避免非同步阻塞的應用。
參考文章:
==========有bug的曲折實現<script language="javascript">
/*Javascript中暫停功能的實現
Javascript本身沒有暫停功能(sleep不能使用)同時 vbscript也不能使用doEvents,故編寫此函式實現此功能。
javascript作為弱物件語言,一個函式也可以作為一個物件使用。
比如:
function Test(){
alert("hellow");
this.NextStep=function(){
alert("NextStep");
}
}
我們可以這樣呼叫 var myTest=new Test();myTest.NextStep();
我們做暫停的時候可以吧一個函式分為兩部分,暫停操作前的不變,把要在暫停後執行的程式碼放在this.NextStep中。
為了控制暫停和繼續,我們需要編寫兩個函式來分別實現暫停和繼續功能。
暫停函式如下:
*/
function Pause(obj,iMinSecond){
if (window.eventList==null) window.eventList=new Array();
var ind=-1;
for (var i=0;i<window.eventList.length;i++){
if (window.eventList[i]==null) {
window.eventList[i]=obj;
ind=i;
break;
}
}
if (ind==-1){
ind=window.eventList.length;
window.eventList[ind]=obj;
}
setTimeout("GoOn(" + ind + ")",1000);
}
/*
該函式把要暫停的函式放到陣列window.eventList裡,同時通過setTimeout來呼叫繼續函式。
繼續函式如下:
*/
function GoOn(ind){
var obj=window.eventList[ind];
window.eventList[ind]=null;
if (obj.NextStep) obj.NextStep();
else obj();
}
/*
該函式呼叫被暫停的函式的NextStep方法,如果沒有這個方法則重新呼叫該函式。
function funcA(){
funcB();
//other code
}
怎麼定義函式B,讓B在執行的時候不僅能終止B本身,而且能終止函式A的執行?
這是個非常規的問題,我們分兩大部分討論. (1.為什麼一定這樣做 2.怎麼實現)
1. 顯然,這種編碼方式已經打亂了正規的程式編寫原則,我們編寫函式的目的就是為了封裝,為了實現程式碼的模組化. 如果B能讓A退出返回, 那這種編碼方式肯怕比濫用 goto 語句還濫了.
這樣做有必要嗎?為什麼一定要這樣做....??
答案如下:
假如我們要擴充套件Array的prototype. 比方說:定義一個 find方法,用來返回第一個讓 執行函式為真的陣列元素.
5 for(var i=0;i<this.length;i++) f(this[i],i,this)
6 }
7 8 Array.prototype.find=function(f){
9 var result;
10 this.each(function(value,index,arr){
11 if (f(value,index,arr)) result=value
12 })
13 return result
14 }
15 16 var arr=[1,2,3,4,5,7,9]
17 18 function foo(v){ //檢測是不是偶數19 return v%2==020 }
21 alert(arr.find(foo))
22 23 </script>
結果另我們大失所望.
首先: 在邏輯上,程式是錯誤的,因為我們期望返回第一個偶數,但是程式卻返回的是最後一個偶數.
其次: 程式的效率是低下的,那怕是找最後一個偶數,他在找到偶數4後,仍然檢測了4後面的所有元素.這個動作
是多餘的.
怎麼辦呢? 請看程式碼中的第11行,如果檢測到 f(value,index,arr) 為真的時候,能夠直接中斷函式 this.each()該多好啊. 效率,結果,雙贏的局面.
所以對於問題一 "為什麼一定這樣做" , 在這裡,具體到這個應用上,有足夠的理由讓函式 B()來中斷函式A()
看到這裡,你可能會問: 你的 find 方法為什麼不這樣寫?
Array.prototype.find=function(f){
for(var i=0;i<this.length;i++){
if (f(this[i],i,this)) return this[i]
}
}
這樣不整個世界都清淨了嗎.
是的,如果我只是簡單的寫一個find 這樣寫肯定沒問題,但是如果現在我正在寫一個複雜的應用,或一個寫一個js框架呢
我要實現一系列的
Array.prototype.all
Array.prototype.any
Array.prototype.each
Array.prototype.map
Array.prototype.find
Array.prototype.findAll
Array.prototype.grep
Array.prototype.inject
...... 詳細請參見 prototype.js v1.4 有上十種方法等著實現呢,我怎不可能每個方法都用 for迴圈一個一個的
遍歷陣列把. 我肯定要實現一個 each 方法作為統一入口吧.
閒話少說,我們來看怎麼解決問題:
要在 B函式中終止A函式,並返回結果, 目前我能想到的辦法就是用異常 try{}catch(x){}
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> 1 <script> 2 // by go_rush(阿舜) @ http://ashun.cnblogs.com 3 4 var $break=new Object()
5 6 Array.prototype.each=function(f){
7 try{
8 for(var i=0;i<this.length;i++){
9 try{
10 f(this[i],i,this)
11 }catch(e){
12 if (e==$break) throw e
13 }
14 }
15 }catch(e){
16 }
17 }
18 19 Array.prototype.find=function(f){
20 var result;
21 this.each(function(value,index,arr){
22 if (f(value,index,arr)){
23 result=value
24 throw $break25 }
26 })
27 return result
28 }
29 30 var arr=[1,2,3,4,5,7,9]
31 32 function foo(v){ //檢測是不是偶數33 return v%2==034 }
35 alert(arr.find(foo))
36 37 </script>
在第24行,如果程式已經找到第一個滿足函式返回值為真的元素,那麼就丟擲一個自定義異常,終止 this.each()的
執行.. 注意第12行,只有確保函式丟擲的是自定義異常才繼續向上丟擲異常,從而終止函式的執行.
在上面的程式碼中,我用的 try---catch方法完全是用來解決本貼所提出的問題的,並未進行任何其他錯誤處理.
在這方面,prototype.js ,通過定義兩個自定義異常物件 $break 和 $continue ,既照顧到了異常處理,又解決了本貼
提出的問題. Enumerable 物件實現得很優雅, 大家不妨再去體會體會 prototype.js 中Enumerable的妙處.
我們看看prototype.js 是怎麼做的,我還是貼出來把
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->var $break=new Object();
var $continue=new Object();
var Enumerable ={
each: function(iterator) {
var index =0;
try{
this._each(function(value) {
try{
iterator(value, index++);
}catch (e) {
if (e != $continue) throw e;
}
});
}catch (e) {
if (e != $break) throw e;
}
},
all: function(iterator) {
var result =true;
this.each(function(value, index) {
result = result &&!!(iterator || Prototype.K)(value, index);
if (!result) throw $break;
});
return result;
},
any: function(iterator) {
var result =true;
this.each(function(value, index) {
if (result =!!(iterator || Prototype.K)(value, index))
throw $break;
});
return result;
},
Feedback
# re: JavaScript寫作技巧,函式A中呼叫函式B, 怎樣在函式B中寫程式碼中斷函式A的執行? 回覆 更多評論
2006-11-29 08:23 by Bao*3 能達到目的就可以, 不過也許自己定義個exception會更好# re: JavaScript寫作技巧,函式A中呼叫函式B, 怎樣在函式B中寫程式碼中斷函式A的執行? 回覆 更多評論
2006-11-29 08:58 by 布林[匿名] 閱讀程式碼的快樂# re: JavaScript寫作技巧,函式A中呼叫函式B, 怎樣在函式B中寫程式碼中斷函式A的執行? 回覆 更多評論
2006-11-29 09:16 by yzx110[匿名] 自定義異常,然後丟擲,可能每個使用each的地方還要try{}catch(e){}感覺還不如迴圈判斷的好
封裝本來就是封裝變化的地方,一個迴圈很穩定,寫很多地方除了麻煩點沒什麼。並且each就不是幹這個事情的,而非讓它幹這個事情,彆扭。
並且相對於閱讀程式碼的人理解迴圈比理解異常中斷要簡單的多
# re: JavaScript寫作技巧,函式A中呼叫函式B, 怎樣在函式B中寫程式碼中斷函式A的執行? 回覆 更多評論
2006-11-29 09:18 by qqhe325 在each裡面加個break;Array.prototype.each=function(f){
for(var i=0;i<this.length;i++) if(f(this[i],i,this)==true) break;//,i,this
}
Array.prototype.find=function(){
var result;
this.each(function(value,index,arr){//
//if (f(value,index,arr)) result=value
//alert(value);
if (foo(value,index,arr)==true)
{
//alert(value);
result=value;
return true;
}
})
return result;
}
var arr=[1,2,3,4,5,7,9];
function foo(v){ //檢測是不是偶數
if(v%2==0)return true;
}
alert(arr.find(foo))