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
不使用編譯方法的話,使用鏈式操作來表征順序,則實現后的樣子應當是這樣的:
Thread(). // 獲取線程 then(do1). // 然后執行do1 wait(1000). // 等待1000ms then(do2). // 然后執行do2 wait(1000). // 等待1000ms then(do3). // 然后執行do3 wait(1000); // 等待1000ms
?
循環執行
循環這很好理解,比如for循環:
for (; true ;){ dosomething (); wait(1000); }
進行無限次循環執行do,并且每次都延遲1000ms。則其鏈式表達應當是這樣的:
Thread(). // 獲取線程 loop( -1 ). // 循環開始,正數則表示循環正數次,負數則表示循環無限次 then( dosomething ). // 然后執行do wait(1000). // 等待1000ms loopEnd(); // 循環結束
這個可以參考后面的例子。?
?
分支執行
分支也就是if...else,比如:
if ( true ){ doSccess(); } else { doFail(); }
那么其鏈式實現應當是:
Thread(). // 獲得線程 right( true ). // 如果表達式正確 then(doSccess). // 執行doSccess left(). // 否則 then(doFail). // 執行doFail leftEnd(). // left分支結束 rightEnd(); // right分支結束
?
聲明變量
聲明變量也就是:
var
a = "hello world!";
可被其它函數使用。那么我們的實現是:
Thread(). // 得到線程 define("hello world!"). // 將回調函數第一個參數設為hello world! then( function (a){alert(a);}); // 獲取變量a,alert出來
?
順序執行實現方案
所謂打包函數就是將回調函數打包后產生的新的函數,舉個例子:
function package(callback){
return function(){
callback();
// 干其他事情
}
}
這樣我們就將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中的例子:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Clock - Wind.js Sample</title> <meta http-equiv="X-UA-Compatible" content="IE=9" /> <script src="http://www.cnblogs.com/../src/wind-core.js"></script> <script src="http://www.cnblogs.com/../src/wind-compiler.js"></script> <script src="http://www.cnblogs.com/../src/wind-builderbase.js"></script> <script src="http://www.cnblogs.com/../src/wind-async.js"></script> <script> var drawHand = function(value, length) { ctx.beginPath(); var angle = Math.PI * 2 * value / 60; var x = Math.sin(angle) * length; var y = Math.cos(angle) * length; ctx.moveTo(100, 100); ctx.lineTo(100 + x, 100 - y); ctx.stroke(); } var drawClock = function(time) { if (!ctx) { var h = time.getHours(); var m = time.getMinutes(); var s = time.getSeconds(); var text = ((h >= 10) ? h : "0" + h) + ":" + ((h >= 10) ? m : "0" + m) + ":" + ((h >= 10) ? s : "0" + s); document.getElementById("clockText").innerHTML = text; return; } ctx.clearRect(0, 0, 200, 200); ctx.beginPath(); ctx.arc(100, 100, 90, 0, Math.PI * 2, false); for (var i = 0; i < 60; i += 5) { var angle = Math.PI * 2 * i / 60; var x = Math.sin(angle); var y = Math.cos(angle); ctx.moveTo(100 + x * 85, 100 - y * 85); ctx.lineTo(100 + x * 90, 100 - y * 90); } ctx.stroke(); drawHand(time.getSeconds(), 80); drawHand(time.getMinutes() + time.getSeconds() * 1.0 / 60, 60); drawHand(time.getHours() % 12 * 5 + time.getMinutes() * 1.0 / 12, 40); } var drawClockAsync = eval(Wind.compile("async", function(interval) { while (true) { drawClock(new Date()); $await(Wind.Async.sleep(interval)); } })); </script> </head> <body> <canvas id="clockCanvas" height="200" width="200"> <div id="clockText" style="font-size:20pt;"></div> </canvas> <script> var canvas = document.getElementById("clockCanvas"); var ctx = canvas.getContext ? canvas.getContext("2d") : null; drawClockAsync(1000).start(); </script> </body> </html>
我的例子:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Clock - asThread.js Sample</title> <meta http-equiv="X-UA-Compatible" content="IE=9" /> <!-- 例子修改自wind.js --> <script src="asThread.js"></script> <script> var drawHand = function(value, length) { ctx.beginPath(); var angle = Math.PI * 2 * value / 60; var x = Math.sin(angle) * length; var y = Math.cos(angle) * length; ctx.moveTo(100, 100); ctx.lineTo(100 + x, 100 - y); ctx.stroke(); } var drawClock = function() { var time = new Date() if (!ctx) { var h = time.getHours(); var m = time.getMinutes(); var s = time.getSeconds(); var text = ((h >= 10) ? h : "0" + h) + ":" + ((h >= 10) ? m : "0" + m) + ":" + ((h >= 10) ? s : "0" + s); document.getElementById("clockText").innerHTML = text; return; } ctx.clearRect(0, 0, 200, 200); ctx.beginPath(); ctx.arc(100, 100, 90, 0, Math.PI * 2, false); for (var i = 0; i < 60; i += 5) { var angle = Math.PI * 2 * i / 60; var x = Math.sin(angle); var y = Math.cos(angle); ctx.moveTo(100 + x * 85, 100 - y * 85); ctx.lineTo(100 + x * 90, 100 - y * 90); } ctx.stroke(); drawHand(time.getSeconds(), 80); drawHand(time.getMinutes() + time.getSeconds() * 1.0 / 60, 60); drawHand(time.getHours() % 12 * 5 + time.getMinutes() * 1.0 / 12, 40); } Thread(). // 使用主線程線程 loop(-1). // 負數表示循環無限多次,如果是正數n,則表示n次循環 then(drawClock). // 循環中運行drawClock wait(1000). // 然后等待1000ms loopEnd(); // 循環結束 // 線程定義結束 </script> </head> <body> <canvas id="clockCanvas" height="200" width="200"> <div id="clockText" style="font-size:20pt;"></div> </canvas> <script> var canvas = document.getElementById("clockCanvas"); var ctx = canvas.getContext ? canvas.getContext("2d") : null; Thread().run(); // 運行線程 </script> </body> </html>
Something more?
- 將事件當成分支處理
我們提供了on方法將事件轉成分支來執行。
舉個例子頁面有個按鈕“點我”,但是我們希望打開頁面5秒內單擊沒有效,5秒后顯示“請點擊按鈕”后,單擊才會出現“你成功點擊了”。
使用on分支是這樣的:
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > < html > < head > < title > on - asThread.js Sample </ title > < meta http-equiv ="X-UA-Compatible" content ="IE=9" /> < script src ="asThread.js" ></ script > </ head > < body > < button id = "b" > 點我 </ button > < script > var ele = document.getElementById( " b " ); Thread(). // 獲得線程 then( function (){alert( " 請點擊按鈕 " )}, 5000 ). // 然后等5秒顯示"請點擊按鈕" on(ele, " click " ). // 事件分支On開始,如果ele觸發了click事件 then( function (){alert( " 你成功點擊了 " )}). // 那么執行你成功點擊了 onEnd(). // 事件分支On結束 then( function (){alert( " 都說可以的了 " )}). // 然后彈出"都說可以的了" run(); // 啟動線程 </ script > </ body > </ html >自定義事件也可以哦,只要在.on時候傳進去注冊監聽函數,和刪除監聽函數就行了。比如:
function addEvent(__elem, __type, __handler){ // 添加監聽 } function removeEvent(__elem, __type, __handler){ // 刪除監聽 } Thread(). on(ele, "success" , addEvent, removeEvent). then( function (){alert("成功!" )}). onEnd(). run();當然實際上我們還可以注冊多個事件分支。事件分支是并列的,也就是平級的事件分支沒有現有順序,所以我們能這樣:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>on - asThread.js Sample</title> <meta http-equiv="X-UA-Compatible" content="IE=9" /> <script src="asThread.js"></script> </head> <body> <button id = "b">點我</button> <button id = "c">點我</button> <script> var ele0 = document.getElementById("b" ), ele1 = document.getElementById("c" ); Thread(). // 獲得線程 then( function (){alert("請點擊按鈕")}, 5000). // 然后等5秒顯示"請點擊按鈕" on(ele0, "click"). // 事件分支On開始,如果ele0觸發了click事件 then( function (){alert("你成功點擊了")}). // 那么執行你成功點擊了 on(ele1, "click"). // 事件分支On開始,如果ele1觸發了click事件 then( function (){alert("你成功點擊了")}). // 那么執行你成功點擊了 onEnd(). // 事件分支On結束 then( function (){alert("都說可以的了")}). // 然后彈出"都說可以的了" run(); // 啟動線程 </script> </body> </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,所以我們提供了傳參可以是函數(但是函數返回值需要是布爾值,否則……╮(╯▽╰)╭也會轉成布爾值的……哈哈)。比如:
fucntion foo( boolean ){ return ! boolean ; } Thread(). define( true ). right(foo). then( function (){ /* 這里不會運行到*/}). rightEnd(). run();
Enjoy yourself!!
?
項目地址
https://github.com/miniflycn/asThread
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
