jQuery中所支持的異步模型為:
- Callbacks,回調(diào)函數(shù)列隊(duì)。
- Deferred,延遲執(zhí)行對(duì)象。
- Promise,是Deferred只暴露非狀態(tài)改變方法的對(duì)象。
這些模型都很漂亮,但我想要一種更帥氣的異步模型。
?
Thread?
我們知道鏈?zhǔn)讲僮魇强梢院芎玫谋碚鬟\(yùn)行順序的(可以參考我的文章《 jQuery鏈?zhǔn)讲僮? 》),然而通常基于回調(diào)函數(shù)或者基于事件監(jiān)聽的異步模型中,代碼的執(zhí)行順序不清晰。
Callbacks模型實(shí)際上類似一個(gè)自定義事件的回調(diào)函數(shù)隊(duì)列,當(dāng)觸發(fā)該事件(調(diào)用Callbacks.fire())時(shí),則回調(diào)隊(duì)列中的所有回調(diào)函數(shù)。
Deferred是個(gè)延遲執(zhí)行對(duì)象,可以注冊(cè)Deferred成功、失敗或進(jìn)行中狀態(tài)的回調(diào)函數(shù),然后通過觸發(fā)相應(yīng)的事件來回調(diào)函數(shù)。
這兩種異步模型都類似于事件監(jiān)聽異步模型,實(shí)質(zhì)上順序依然是分離的。
當(dāng)然Promise看似能提供我需要的東西,比如Promise.then().then().then()。但是,Promise雖然成功用鏈?zhǔn)讲僮髅鞔_了異步編程的順序執(zhí)行,但是沒有循環(huán),成功和失敗分支是通過內(nèi)部代碼確定的。
個(gè)人認(rèn)為,Promise是為了規(guī)范化后端nodejs中I/O操作異步模型的,因?yàn)镮/O狀態(tài)只有成功和失敗兩種狀態(tài),所以他是非常成功的。
但在前端,要么只有成功根本沒有失敗,要么不止只有兩種狀態(tài),不應(yīng)當(dāng)固定只提供三種狀態(tài)的方案,我覺得應(yīng)該提供可表征多狀態(tài)的異步方案。
這個(gè)大家可以在something more看到。
我想要一種類似于線程的模型,我們?cè)谶@里稱為Thread,也就是他能順序執(zhí)行、也能循環(huán)執(zhí)行、當(dāng)然還有分支執(zhí)行。
?
順序執(zhí)行
線程的順序執(zhí)行流程,也就是類似于:
do1();
do2();
do3();
這樣就是依次執(zhí)行do1,do2,do3。因?yàn)檫@是異步模型,所以我們希望能添加wait方法,即類似于:
do1(); wait( 1000); // 等待1000ms do2(); wait( 1000); // 等待1000ms do3(); wait( 1000); // 等待1000ms
不使用編譯方法的話,使用鏈?zhǔn)讲僮鱽肀碚黜樞颍瑒t實(shí)現(xiàn)后的樣子應(yīng)當(dāng)是這樣的:
Thread(). // 獲取線程 then(do1). // 然后執(zhí)行do1 wait(1000). // 等待1000ms then(do2). // 然后執(zhí)行do2 wait(1000). // 等待1000ms then(do3). // 然后執(zhí)行do3 wait(1000); // 等待1000ms
?
循環(huán)執(zhí)行
循環(huán)這很好理解,比如for循環(huán):
for (; true ;){ dosomething (); wait(1000); }
進(jìn)行無限次循環(huán)執(zhí)行do,并且每次都延遲1000ms。則其鏈?zhǔn)奖磉_(dá)應(yīng)當(dāng)是這樣的:
Thread(). // 獲取線程 loop( -1 ). // 循環(huán)開始,正數(shù)則表示循環(huán)正數(shù)次,負(fù)數(shù)則表示循環(huán)無限次 then( dosomething ). // 然后執(zhí)行do wait(1000). // 等待1000ms loopEnd(); // 循環(huán)結(jié)束
這個(gè)可以參考后面的例子。?
?
分支執(zhí)行
分支也就是if...else,比如:
if ( true ){ doSccess(); } else { doFail(); }
那么其鏈?zhǔn)綄?shí)現(xiàn)應(yīng)當(dāng)是:
Thread(). // 獲得線程 right( true ). // 如果表達(dá)式正確 then(doSccess). // 執(zhí)行doSccess left(). // 否則 then(doFail). // 執(zhí)行doFail leftEnd(). // left分支結(jié)束 rightEnd(); // right分支結(jié)束
?
聲明變量
聲明變量也就是:
var
a = "hello world!";
可被其它函數(shù)使用。那么我們的實(shí)現(xiàn)是:
Thread(). // 得到線程 define("hello world!"). // 將回調(diào)函數(shù)第一個(gè)參數(shù)設(shè)為hello world! then( function (a){alert(a);}); // 獲取變量a,alert出來
?
順序執(zhí)行實(shí)現(xiàn)方案
Thread實(shí)際上是一個(gè)打包函數(shù)Fn隊(duì)列。
所謂打包函數(shù)就是將回調(diào)函數(shù)打包后產(chǎn)生的新的函數(shù),舉個(gè)例子:
function package(callback){
return function(){
callback();
// 干其他事情
}
}
這樣我們就將callback函數(shù)打包起來了。
Thread提供一個(gè)fire方法來觸發(fā)線程取出一個(gè)打包函數(shù)然后執(zhí)行,打包函數(shù)執(zhí)行以后回調(diào)Thread的fire方法。
那么我們就可以順序執(zhí)行函數(shù)了。
現(xiàn)在只要打包的時(shí)候設(shè)置setTimeout執(zhí)行,則這個(gè)線程就能實(shí)現(xiàn)wait方法了。
?
循環(huán)執(zhí)行實(shí)現(xiàn)方案
循環(huán)Loop是一個(gè)Thread的變形,只不過在執(zhí)行里面的打包函數(shù)的時(shí)候使用另外一種方案,通過添加一個(gè)指針取出,執(zhí)行完后觸發(fā)Loop繼續(xù),移動(dòng)指針取出下一個(gè)打包函數(shù)。
?
分支執(zhí)行實(shí)現(xiàn)方案
分支Right和Left也是Thread的一種變形,開啟分支的時(shí)候,主Thread會(huì)創(chuàng)建兩個(gè)分支Right線程和Left線程,打包一個(gè)觸發(fā)分支Thread的函數(shù)推入隊(duì)列,然后當(dāng)執(zhí)行到該函數(shù)的時(shí)候判斷觸發(fā)哪個(gè)分支執(zhí)行。
其中一個(gè)隊(duì)列執(zhí)行結(jié)束后回調(diào)主Thread,通知進(jìn)行下一步。?
?
例子
由于該方案和wind-asycn非常相似,所以我們拿wind.js中的clock例子進(jìn)行改造看看其中的差別吧。
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). // 負(fù)數(shù)表示循環(huán)無限多次,如果是正數(shù)n,則表示n次循環(huán) then(drawClock). // 循環(huán)中運(yùn)行drawClock wait(1000). // 然后等待1000ms loopEnd(); // 循環(huán)結(jié)束 // 線程定義結(jié)束 </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(); // 運(yùn)行線程 </script> </body> </html>
Something more?
- 將事件當(dāng)成分支處理
我們提供了on方法將事件轉(zhuǎn)成分支來執(zhí)行。
舉個(gè)例子頁(yè)面有個(gè)按鈕“點(diǎn)我”,但是我們希望打開頁(yè)面5秒內(nèi)單擊沒有效,5秒后顯示“請(qǐng)點(diǎn)擊按鈕”后,單擊才會(huì)出現(xiàn)“你成功點(diǎn)擊了”。
使用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" > 點(diǎn)我 </ button > < script > var ele = document.getElementById( " b " ); Thread(). // 獲得線程 then( function (){alert( " 請(qǐng)點(diǎn)擊按鈕 " )}, 5000 ). // 然后等5秒顯示"請(qǐng)點(diǎn)擊按鈕" on(ele, " click " ). // 事件分支On開始,如果ele觸發(fā)了click事件 then( function (){alert( " 你成功點(diǎn)擊了 " )}). // 那么執(zhí)行你成功點(diǎn)擊了 onEnd(). // 事件分支On結(jié)束 then( function (){alert( " 都說可以的了 " )}). // 然后彈出"都說可以的了" run(); // 啟動(dòng)線程 </ script > </ body > </ html >自定義事件也可以哦,只要在.on時(shí)候傳進(jìn)去注冊(cè)監(jiān)聽函數(shù),和刪除監(jiān)聽函數(shù)就行了。比如:
function addEvent(__elem, __type, __handler){ // 添加監(jiān)聽 } function removeEvent(__elem, __type, __handler){ // 刪除監(jiān)聽 } Thread(). on(ele, "success" , addEvent, removeEvent). then( function (){alert("成功!" )}). onEnd(). run();當(dāng)然實(shí)際上我們還可以注冊(cè)多個(gè)事件分支。事件分支是并列的,也就是平級(jí)的事件分支沒有現(xiàn)有順序,所以我們能這樣:
<!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">點(diǎn)我</button> <button id = "c">點(diǎn)我</button> <script> var ele0 = document.getElementById("b" ), ele1 = document.getElementById("c" ); Thread(). // 獲得線程 then( function (){alert("請(qǐng)點(diǎn)擊按鈕")}, 5000). // 然后等5秒顯示"請(qǐng)點(diǎn)擊按鈕" on(ele0, "click"). // 事件分支On開始,如果ele0觸發(fā)了click事件 then( function (){alert("你成功點(diǎn)擊了")}). // 那么執(zhí)行你成功點(diǎn)擊了 on(ele1, "click"). // 事件分支On開始,如果ele1觸發(fā)了click事件 then( function (){alert("你成功點(diǎn)擊了")}). // 那么執(zhí)行你成功點(diǎn)擊了 onEnd(). // 事件分支On結(jié)束 then( function (){alert("都說可以的了")}). // 然后彈出"都說可以的了" run(); // 啟動(dòng)線程 </script> </body> </html>
- 開辟多個(gè)線程
一個(gè)線程不夠用?只要輸入名字就能開辟或者得到線程了。
系統(tǒng)會(huì)自動(dòng)初始化一個(gè)主線程,當(dāng)不傳參數(shù)時(shí)就直接返回主線程:
Thread() // 得到主線程但如果主線程正在用想開辟一個(gè)線程時(shí),只要給個(gè)名字就行,比如:
Thread("hello") // 得到名字是hello的線程那么下次再想用該線程時(shí)只要輸入相同的名字就行了:
Thread("hello") // 得到hello線程默認(rèn)只最多只提供10個(gè)線程,所以用完記得刪掉:
Thread("hello").del();
- setImmediate
IE10已經(jīng)提供了setImmediate方法,而其他現(xiàn)代瀏覽器也可以模擬該方法,其原理是推倒線程末端,使得瀏覽器畫面能渲染,得到比setTimeout(0)更快的響應(yīng)。
我們通過接口.imm來提供這一功能。比如:
Thread(). imm( function (){alert("hello world" )}). run();這方法和.then(fn)不太一樣,.then(fn)是可能阻塞當(dāng)前瀏覽器線程的,但.imm(fn)是將處理推到瀏覽器引擎列隊(duì)末端,排到隊(duì)了在運(yùn)行。
所以如果你使用多個(gè)Thread(偽多線程),而又希望確保線程是并行運(yùn)行的,那么請(qǐng)使用.imm來替代.then。
當(dāng)然對(duì)于老版IE,只能用setTimeout(0)替代了。
- 分支參數(shù)可以是函數(shù)
分支Right傳的參數(shù)如果只是布爾值肯定很不爽,因?yàn)檫@意味著分支是靜態(tài)的,在初始化時(shí)候就決定了,但我們希望分支能在執(zhí)行到的時(shí)候再判斷是走Right還是Left,所以我們提供了傳參可以是函數(shù)(但是函數(shù)返回值需要是布爾值,否則……╮(╯▽╰)╭也會(huì)轉(zhuǎn)成布爾值的……哈哈)。比如:
fucntion foo( boolean ){ return ! boolean ; } Thread(). define( true ). right(foo). then( function (){ /* 這里不會(huì)運(yùn)行到*/}). rightEnd(). run();
Enjoy yourself!!
?
項(xiàng)目地址
https://github.com/miniflycn/asThread
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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