除了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 能提供原生的線程支持之前,或許我們可以改變設計以避免異步阻塞的應用。
參考文章:
Agile Ajax - Narrative Javascript - Cleaner Code for Long Running Tasks
FAQTs - How do I pause execution in 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 Test() { alert("hellow"); Pause(this,1000);//調用暫停函數 this.NextStep=function() { alert("NextStep"); } } </script>
Javascript順序執行的實現:
http://www.cnlei.org/blog/article.asp?id=297
JavaScript系列-同步還是異步:
http://blog.iecn.net/blog/html/do-showone-tid-966.html
Javascript中暫停功能的實現 :
http://blog.csdn.net/snakegod/archive/2004/09/22/112810.aspx
JavaScript Sleep函數 :
http://blog.csdn.net/gaooo/archive/2007/02/25/1514096.aspx
該文章轉載自腳本之家:http://www.jb51.net/html/200703/23/7505.htm
可暫停的滾動公告板
http://www.codebit.cn/pub/html/javascript/tip/pausing_up_down_scroller/
二、A函數調用B函數,B不僅能控制自身,也可以讓A來控制它
function funcA() { funcB(); //other code }
怎么定義函數B,讓B在運行的時候不僅能終止B本身,而且能終止函數A的運行?
這是個非常規的問題,我們分兩大部分討論. (1.為什么一定這樣做 2.怎么實現)
1. 顯然,這種編碼方式已經打亂了正規的程序編寫原則,我們編寫函數的目的就是為了封裝,為了實現代碼的模塊化. 如果B能讓A退出返回, 那這種編碼方式肯怕比濫用 goto 語句還濫了.
這樣做有必要嗎?為什么一定要這樣做....??
答案如下:
假如我們要擴展Array的prototype. 比方說:定義一個 find方法,用來返回第一個讓 執行函數為真的數組元素.
<script> // by go_rush(阿舜) @ http://ashun.cnblogs.com Array.prototype.each=function(f) { for(var i=0;i<this.length;i++) f(this[i],i,this); } Array.prototype.find=function(f) { var result; this.each(function(value,index,arr){if (f(value,index,arr))result=value;}) return result; } var arr=[1,2,3,4,5,7,9] function foo(v) { //檢測是不是偶數 return v%2==0; } alert(arr.find(foo)); </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){}
實現代碼
<script> // by go_rush(阿舜) @ http://ashun.cnblogs.com var $break=new Object() Array.prototype.each=function(f){ try{ for(var i=0;i<this.length;i++){ try{ f(this[i],i,this) }catch(e){ if (e==$break) throw e } } }catch(e){ } } Array.prototype.find=function(f){ var result; this.each(function(value,index,arr){ if (f(value,index,arr)){ result=value throw $break } }) return result } var arr=[1,2,3,4,5,7,9] function foo(v){ //檢測是不是偶數 return v%2==0 } alert(arr.find(foo)) </script>
在第24行,如果程序已經找到第一個滿足函數返回值為真的元素,那么就拋出一個自定義異常,終止 this.each()的
運行.. 注意第12行,只有確保函數拋出的是自定義異常才繼續向上拋出異常,從而終止函數的運行.
在上面的代碼中,我用的 try---catch方法完全是用來解決本貼所提出的問題的,並未進行任何其他錯誤處理.
在這方面,prototype.js ,通過定義兩個自定義異常對象 $break 和 $continue ,既照顧到了異常處理,又解決了本貼
提出的問題. Enumerable 對象實現得很優雅, 大家不妨再去體會體會 prototype.js 中Enumerable的妙處.
我們看看prototype.js 是怎么做的,我還是貼出來把
prototype.js的代碼片段摘取
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; },