一種模仿線程的Javascript異步模型設計&實現
jQuery中所支持的異步模型為:
- Callbacks,回調函數列隊。
- Deferred,延遲執行對象。
- Promise,是Deferred只暴露非狀態改變方法的對象。
這些模型都很漂亮,但我想要一種更帥氣的異步模型。
Thread?
我們知道鏈式操作是可以很好的表征運行順序的(可以參考我的文章《jQuery鏈式操作》),然而通常基于回調函數或者基于事件監聽的異步模型中,代碼的執行順序不清晰。
Callbacks模型實際上類似一個自定義事件的回調函數隊列,當觸發該事件(調用Callbacks.fire())時,則回調隊列中的所有回調函數。
Deferred是個延遲執行對象,可以注冊Deferred成功、失敗或進行中狀態的回調函數,然后通過觸發相應的事件來回調函數。
這兩種異步模型都類似于事件監聽異步模型,實質上順序依然是分離的。
當然Promise看似能提供我需要的東西,比如Promise.then().then().then()。但是,Promise雖然成功用鏈式操作明確了異步編程的順序執行,但是沒有循環,成功和失敗分支是通過內部代碼確定的。
個人認為,Promise是為了規范化后端nodejs中I/O操作異步模型的,因為I/O狀態只有成功和失敗兩種狀態,所以他是非常成功的。
但在前端,要么只有成功根本沒有失敗,要么不止只有兩種狀態,不應當固定只提供三種狀態的方案,我覺得應該提供可表征多狀態的異步方案。
這個大家可以在something more看到。
我想要一種類似于線程的模型,我們在這里稱為Thread,也就是他能順序執行、也能循環執行、當然還有分支執行。
順序執行
線程的順序執行流程,也就是類似于:
do1(); do2(); do3();
這樣就是依次執行do1,do2,do3。因為這是異步模型,所以我們希望能添加wait方法,即類似于:
do2(); wait(1000); //等待1000ms
do3(); wait(1000); //等待1000ms
不使用編譯方法的話,使用鏈式操作來表征順序,則實現后的樣子應當是這樣的:
2 then(do1). //然后執行do1
3 wait(1000). //等待1000ms
4 then(do2). //然后執行do2
5 wait(1000). //等待1000ms
6 then(do3). //然后執行do3
7 wait(1000); //等待1000ms
循環執行
循環這很好理解,比如for循環:
2 dosomething();
3 wait(1000);
4 }
進行無限次循環執行do,并且每次都延遲1000ms。則其鏈式表達應當是這樣的:
2 loop(-1). //循環開始,正數則表示循環正數次,負數則表示循環無限次
3 then(dosomething). //然后執行do
4 wait(1000). //等待1000ms
5 loopEnd(); //循環結束
這個可以參考后面的例子。
分支執行
分支也就是if...else,比如:
2 doSccess();
3 }else{
4 doFail();
5 }
6
7
那么其鏈式實現應當是:
2 right(true). //如果表達式正確
3 then(doSccess). //執行doSccess
4 left(). //否則
5 then(doFail). //執行doFail
6 leftEnd(). //left分支結束
7 rightEnd(); //right分支結束
聲明變量
聲明變量也就是:
var a = "hello world!";
可被其它函數使用。那么我們的實現是:
2 define("hello world!"). //將回調函數第一個參數設為hello world!
3 then(function(a){alert(a);}); //獲取變量a,alert出來
順序執行實現方案
Thread實際上是一個打包函數Fn隊列。
所謂打包函數就是將回調函數打包后產生的新的函數,舉個例子:
2 return function(){
3 callback();
4 // 干其他事情
5 }
6 }
這樣我們就將callback函數打包起來了。
Thread提供一個fire方法來觸發線程取出一個打包函數然后執行,打包函數執行以后回調Thread的fire方法。
那么我們就可以順序執行函數了。
現在只要打包的時候設置setTimeout執行,則這個線程就能實現wait方法了。
循環執行實現方案
循環Loop是一個Thread的變形,只不過在執行里面的打包函數的時候使用另外一種方案,通過添加一個指針取出,執行完后觸發Loop繼續,移動指針取出下一個打包函數。
分支執行實現方案
分支Right和Left也是Thread的一種變形,開啟分支的時候,主Thread會創建兩個分支Right線程和Left線程,打包一個觸發分支Thread的函數推入隊列,然后當執行到該函數的時候判斷觸發哪個分支執行。
其中一個隊列執行結束后回調主Thread,通知進行下一步。
例子
由于該方案和wind-asycn非常相似,所以我們拿wind.js中的clock例子進行改造看看其中的差別吧。
wind.js中的例子:
我的例子:
Something more?
- 將事件當成分支處理
我們提供了on方法將事件轉成分支來執行。
舉個例子頁面有個按鈕“點我”,但是我們希望打開頁面5秒內單擊沒有效,5秒后顯示“請點擊按鈕”后,單擊才會出現“你成功點擊了”。
使用on分支是這樣的:
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2 <html>
3 <head>
4 <title>on - asThread.js Sample</title>
5 <meta http-equiv="X-UA-Compatible" content="IE=9" />
6 <script src="asThread.js"></script>
7 </head>
8 <body>
9 <button id = "b">點我</button>
10 <script>
11 var ele = document.getElementById("b");
12
13 Thread(). // 獲得線程
14 then(function(){alert("請點擊按鈕")}, 5000). //然后等5秒顯示"請點擊按鈕"
15 on(ele, "click"). // 事件分支On開始,如果ele觸發了click事件
16 then(function(){alert("你成功點擊了")}). //那么執行你成功點擊了
17 onEnd(). // 事件分支On結束
18 then(function(){alert("都說可以的了")}). // 然后彈出"都說可以的了"
19 run(); //啟動線程
20 </script>
21 </body>
22 </html>自定義事件也可以哦,只要在.on時候傳進去注冊監聽函數,和刪除監聽函數就行了。比如:
1 function addEvent(__elem, __type, __handler){
2 //添加監聽
3 }
4
5 function removeEvent(__elem, __type, __handler){
6 //刪除監聽
7 }
8
9 Thread().
10 on(ele, "success", addEvent, removeEvent).
11 then(function(){alert("成功!")}).
12 onEnd().
13 run();當然實際上我們還可以注冊多個事件分支。事件分支是并列的,也就是平級的事件分支沒有現有順序,所以我們能這樣:
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2 <html>
3 <head>
4 <title>on - asThread.js Sample</title>
5 <meta http-equiv="X-UA-Compatible" content="IE=9" />
6 <script src="asThread.js"></script>
7 </head>
8 <body>
9 <button id = "b">點我</button>
10 <button id = "c">點我</button>
11 <script>
12 var ele0 = document.getElementById("b"),
13 ele1 = document.getElementById("c");
14
15 Thread(). // 獲得線程
16 then(function(){alert("請點擊按鈕")}, 5000). //然后等5秒顯示"請點擊按鈕"
17 on(ele0, "click"). // 事件分支On開始,如果ele0觸發了click事件
18 then(function(){alert("你成功點擊了")}). //那么執行你成功點擊了
19 onEnd(). // 事件分支On結束
20 on(ele1, "click"). // 事件分支On開始,如果ele1觸發了click事件
21 then(function(){alert("你成功點擊了")}). //那么執行你成功點擊了
22 onEnd(). // 事件分支On結束
23 then(function(){alert("都說可以的了")}). // 然后彈出"都說可以的了"
24 run(); //啟動線程
25 </script>
26 </body>
27 </html>
- 開辟多個線程
一個線程不夠用?只要輸入名字就能開辟或者得到線程了。
系統會自動初始化一個主線程,當不傳參數時就直接返回主線程:
Thread() //得到主線程但如果主線程正在用想開辟一個線程時,只要給個名字就行,比如:
Thread("hello") //得到名字是hello的線程那么下次再想用該線程時只要輸入相同的名字就行了:
Thread("hello") //得到hello線程默認只最多只提供10個線程,所以用完記得刪掉:
Thread("hello").del();
- setImmediate
IE10已經提供了setImmediate方法,而其他現代瀏覽器也可以模擬該方法,其原理是推倒線程末端,使得瀏覽器畫面能渲染,得到比setTimeout(0)更快的響應。
我們通過接口.imm來提供這一功能。比如:
Thread(). imm(function(){alert("hello world")}). run();這方法和.then(fn)不太一樣,.then(fn)是可能阻塞當前瀏覽器線程的,但.imm(fn)是將處理推到瀏覽器引擎列隊末端,排到隊了在運行。
所以如果你使用多個Thread(偽多線程),而又希望確保線程是并行運行的,那么請使用.imm來替代.then。
當然對于老版IE,只能用setTimeout(0)替代了。
- 分支參數可以是函數
分支Right傳的參數如果只是布爾值肯定很不爽,因為這意味著分支是靜態的,在初始化時候就決定了,但我們希望分支能在執行到的時候再判斷是走 Right還是Left,所以我們提供了傳參可以是函數(但是函數返回值需要是布爾值,否則……╮(╯▽╰)╭也會轉成布爾值的……哈哈)。比如:
1 fucntion foo(boolean){
2 return !boolean;
3 }
4
5 Thread().
6 define(true).
7 right(foo).
8 then(function(){/*這里不會運行到*/}).
9 rightEnd().
10 run();
Enjoy yourself!!
項目地址
http://www.cnblogs.com/justany/archive/2013/01/25/2874602.html