1. 程式人生 > >JS中的sleep 、順序執行

JS中的sleep 、順序執行

最近升級BLOG,需要在JS裡實現暫停功能,還是小有些擾人,總達不到預期的效果,要麼是將函式拆分為幾個部分,要麼採用事件機制,其實單執行緒是沒有多執行緒的sleep功能,所以也只能這樣過程Check了! 除了Narrative JS,jwacs(Javascript With Advanced Continuation Support) 也致力於通過擴充套件JavaScript語法來避免編寫讓人頭痛的非同步呼叫的回撥函式。用jwacs 實現的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方法,用來返回第一個讓 執行函式為真的陣列元素.

 1 <script> 2 // by go_rush(阿舜) @ http://ashun.cnblogs.com 3  4 Array.prototype.each=function(f){
 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==$breakthrow 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 是怎麼做的,我還是貼出來把

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 != $continuethrow e;
        }

      }
);
    }
catch (e) {
      
if (e != $breakthrow 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))