海水正藍

          面朝大海,春暖花開
          posts - 145, comments - 29, trackbacks - 0, articles - 1
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理
          隨筆- 50  文章- 0  評論- 180 

          一種模仿線程的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方法,即類似于:

          do1(); wait(1000);    //等待1000ms 
          do2(); wait(1000);    //等待1000ms 
          do3(); wait(1000);    //等待1000ms

          不使用編譯方法的話,使用鏈式操作來表征順序,則實現后的樣子應當是這樣的:

          1 Thread().    //獲取線程
          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循環:

          1 for(; true;){
          2     dosomething();
          3     wait(1000);
          4 }

          進行無限次循環執行do,并且每次都延遲1000ms。則其鏈式表達應當是這樣的:

          1 Thread().    //獲取線程
          2 loop(-1).    //循環開始,正數則表示循環正數次,負數則表示循環無限次
          3     then(dosomething).    //然后執行do
          4     wait(1000).    //等待1000ms
          5 loopEnd();    //循環結束

          這個可以參考后面的例子。 

           

          分支執行

          分支也就是if...else,比如:

          1 if(true){
          2     doSccess();
          3 }else{
          4     doFail();
          5 }
          6 
          7 

          那么其鏈式實現應當是:

           

          1 Thread().    //獲得線程
          2 right(true).    //如果表達式正確
          3     then(doSccess).    //執行doSccess
          4 left().    //否則
          5     then(doFail).    //執行doFail
          6 leftEnd().    //left分支結束
          7 rightEnd();    //right分支結束

          聲明變量

          聲明變量也就是:

          var a = "hello world!";

          可被其它函數使用。那么我們的實現是:

           

          1 Thread().    //得到線程
          2 define("hello world!").    //將回調函數第一個參數設為hello world!
          3 then(function(a){alert(a);});    //獲取變量a,alert出來

          順序執行實現方案

          Thread實際上是一個打包函數Fn隊列。

          所謂打包函數就是將回調函數打包后產生的新的函數,舉個例子:

          1 function package(callback){
          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!!

           

          項目地址

          https://github.com/miniflycn/asThread

          原問出自:
          http://www.cnblogs.com/justany/archive/2013/01/25/2874602.html

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 信宜市| 武穴市| 商洛市| 廊坊市| 德兴市| 沙洋县| 丰原市| 略阳县| 鹿邑县| 怀安县| 天水市| 苏尼特右旗| 临夏县| 抚顺市| 莆田市| 获嘉县| 洛川县| 邻水| 浏阳市| 明水县| 专栏| 左贡县| 金乡县| 溧阳市| 阿图什市| 东莞市| 乌审旗| 忻城县| 乳山市| 广河县| 新乡市| 志丹县| 晋中市| 无棣县| 怀柔区| 虞城县| 南宁市| 田林县| 马尔康县| 谢通门县| 丰城市|