1、引子
引出本次討論的原因是dojo.io.IframeIO的問(wèn)題。在一個(gè)比較大的Javascript應(yīng)用中使用了dojo,dojo.io是dojo實(shí)現(xiàn)的非常好的一個(gè)地方,因?yàn)閐ojo.io用一個(gè)通用的接口封裝了XmlHttp、Iframe、ScriptSrc這幾種主流方式(Facade模式),是處理Ajax應(yīng)用IO的很好選擇。但是項(xiàng)目實(shí)際部署以后發(fā)現(xiàn)經(jīng)常出現(xiàn)操作無(wú)響應(yīng)的情況,經(jīng)過(guò)反復(fù)排查發(fā)現(xiàn)是使用的dojo.io.IframeIO的排隊(duì)沒(méi)有超時(shí)造成的。
那么我們就要尋找問(wèn)題再哪里呢?
2、問(wèn)題
首先我們看到了dojo.io的bind方法中支持通用的timeout和timeoutSeconds參數(shù),這兩個(gè)參數(shù)從語(yǔ)意上看是為了給你的IO增加超時(shí)機(jī)制。當(dāng)初Ajaxian上有一篇介紹實(shí)際部署的Ajax項(xiàng)目需要注意的穩(wěn)定性問(wèn)題,其中比較重要的一條就是要給你的通訊排隊(duì)增加超時(shí),而dojo.io的這種設(shè)計(jì)正體現(xiàn)了這種觀點(diǎn)。那么我們還是首先分析一下為什么要排隊(duì)和為什么要超時(shí)機(jī)制。
io通訊分同步和異步兩種方式,同步即需要阻塞等待,異步即無(wú)阻塞等待(異步回調(diào))。我們?cè)谑褂肵ML Http的時(shí)候可以選擇asynchronous為true和false,大家都知道它們就是是否同步。使用的地方大家都很熟悉了,比如兩個(gè)function有關(guān),它們的調(diào)用需要保證嚴(yán)格順序,那么就應(yīng)該同步通訊,前一個(gè)完了再調(diào)后一個(gè)。反之如果幾個(gè)function之間無(wú)關(guān),那么就可以異步通訊,減少用戶(hù)瀏覽器的阻塞和等待。
OK,這種模型很清楚和簡(jiǎn)單。可是,實(shí)際上就XMLHttp而言,我們一般盡量不使用asynchronous=false,因?yàn)檫@樣瀏覽器會(huì)在傳輸?shù)臅r(shí)候失去對(duì)用戶(hù)的響應(yīng),用戶(hù)感覺(jué)比較差。對(duì)于需要嚴(yán)格順序的通訊我們使用另外一種方式,那就是回調(diào)的時(shí)候再調(diào)用下面一個(gè)函數(shù),這樣也可以保持嚴(yán)格順序,形成調(diào)用鏈。這種方式其實(shí)就是排隊(duì)模型 。在dojo中有一個(gè)queueBind方法,實(shí)際上就是包裝過(guò)的調(diào)用鏈。有了這些機(jī)制就可以滿(mǎn)足我們?nèi)粘5木幊棠P土耍坪鹾芡昝懒恕?br>可是,問(wèn)題在于并非所有io都可以提供排隊(duì)和非排隊(duì)的方式進(jìn)行通訊。比如iframe和ScriptSrc的方式實(shí)際上都是不支持非排隊(duì)的,因?yàn)檫@些通訊本質(zhì)上都是同步的(或者說(shuō)假異步),在只有一個(gè)Dom元素(iframe或者script元素)進(jìn)行通訊的時(shí)候其實(shí)所有的io都必須排隊(duì)。也就是說(shuō)可憐的iframe和scirptSrc方式都只有排隊(duì)……而不能不排隊(duì)……。這似乎也沒(méi)什么問(wèn)題,但是問(wèn)題來(lái)了……
HTTP通訊并非可靠的通訊,它們都是最大努力的傳輸方式,也就是說(shuō)丟包是不可避免的,在發(fā)生丟包的時(shí)候如果恰巧你的通訊進(jìn)行了排隊(duì)而此時(shí)又沒(méi)有超時(shí)……那么你慘了,就出現(xiàn)了北京早晚場(chǎng)出現(xiàn)的堵車(chē)現(xiàn)象,往往僅僅是一輛輛車(chē)刮蹭了,后面的車(chē)就永久的成為了化石,多么的悲壯呀。
所以,排隊(duì)的超時(shí)就非常重要了!一次通訊一定要有超時(shí)時(shí)間,這樣在阻塞時(shí)可以把阻塞消除。
dojo在BrowserIO(XMLHttp)和ScriptSrcIO里面實(shí)現(xiàn)了超時(shí)機(jī)制,但是在IframeIO中沒(méi)有提供,這可能是個(gè)歷史遺留。
而引子中所說(shuō)的問(wèn)題正來(lái)自這個(gè)不公平的待遇,iFrame的bind實(shí)際上就是queueBind(因?yàn)閐ojo的iFrameIO只用了一個(gè)iFrame),而又沒(méi)有超時(shí)機(jī)制……,然后應(yīng)用部署在實(shí)際網(wǎng)絡(luò)條件后就有很多用戶(hù)遇到了操作無(wú)響應(yīng)的問(wèn)題。
3、解決
找到問(wèn)題并理清思路離解決就非常近了。
dojo的BrowserIO(XMLHttp)和ScriptSrcIO都使用了一種類(lèi)似onFlight的方式檢查超時(shí)。就是紀(jì)錄通訊開(kāi)始時(shí)間,然后傳輸時(shí)開(kāi)始輪訓(xùn)檢查是否超時(shí)。不直接用setTimeout檢查通訊超時(shí)是因?yàn)槿绻卸鄠€(gè)ScriptSrc或者XMLHttp通訊的時(shí)候可以防止互相沖突。
我們也可以比較簡(jiǎn)單的向IframeIO中添加添加這樣的功能:
OK,這里我偷懶了……。因?yàn)槲覀兊捻?xiàng)目只有一個(gè)iFrame作io,所以我們沒(méi)有使用onFlight方式,使用了setTimeout直接檢查的方式。大家先笑納,待簡(jiǎn)單修正把IframeIO也修改為onFlight的方式(然后提交dojo?)。
如此看有點(diǎn)虎頭蛇尾,實(shí)際上就是這樣,問(wèn)題很大解決方法很小。我們經(jīng)常遇到的就是這樣的問(wèn)題。
4、尾聲
最后其實(shí)還是要回到j(luò)avascript應(yīng)用中的io問(wèn)題上來(lái)。這里我還要簡(jiǎn)單寫(xiě)一下我們常用到的io方式,我身邊還有很多同事不了解所以然。
XML Http是這幾個(gè)io中最好的一個(gè),因?yàn)樗且粋€(gè)完整的IO實(shí)現(xiàn),有狀態(tài)回調(diào)機(jī)制,有abort等處理方法,但是相對(duì)來(lái)說(shuō)有幾個(gè)限制:瀏覽器支持、文件上傳、跨域。
瀏覽器支持現(xiàn)在倒不算個(gè)問(wèn)題,Ajax應(yīng)用普遍是IE 5.5+、FF 1.0+、Opera 8+這樣子,它們都支持XMLHttp了。文件上傳用XMLHttp也比較難解決,雖然可以用客戶(hù)端切分的方法,但是用的人實(shí)際上很少(在這點(diǎn)上技術(shù)復(fù)雜性超過(guò)了iframe很多)。
XMLHttp最大的限制是跨域,很?chē)?yán)格,甚至不能跨子域,這給我們的通訊帶來(lái)了巨大的限制。因?yàn)楹芏鄷r(shí)候我們要使用proxy或者負(fù)載均衡的分布式來(lái)解決這個(gè)問(wèn)題,這就增加了部署復(fù)雜性。
相對(duì)來(lái)說(shuō)iframe則放松了這樣的限制,你可以跨子域(比如www.abc.com和ajax.abc.com和cde.xxx.abc.com),我們可以在文檔里面通過(guò)domain="abc.com"來(lái)強(qiáng)制用根域做js執(zhí)行的域。但是這種做法也有麻煩,比如Firefox 1.0.x的一些版本如果domain="abc.com"執(zhí)行以后會(huì)造成XMLHttp通訊失敗,此時(shí)我們就沒(méi)法在用iframe的同時(shí)用XMLHttp了。
iframe的另外一種好處是它比較容易解決文件上傳的問(wèn)題,只要對(duì)form指向一下target為iframe就可以無(wú)刷新上傳了,這幾乎是最簡(jiǎn)單的方案。
但是iframe的限制還是滿(mǎn)多的,它比較復(fù)雜,在Tencent這樣的倒霉瀏覽器中設(shè)置了所有鏈接都在新窗口中打開(kāi)后會(huì)讓?xiě)?yīng)用很難看并不可用,它沒(méi)有完全的解決跨域問(wèn)題等等。
為了解決iframe的不能跨根域的問(wèn)題,引入了ScriptSrc的方式。就是指<script src="xxx.js" id="scriptIo"></script>,然后動(dòng)態(tài)修改src這樣的方式。為了瀏覽器兼容,此種方式下需要?jiǎng)討B(tài)的創(chuàng)建script節(jié)點(diǎn),然后再刪除,否則內(nèi)容不會(huì)載入(非IE的情況)。所以技術(shù)復(fù)雜度相對(duì)也比較高。但是這種方式可以完全解決跨域問(wèn)題,你可以將src指向任何地方!
那么缺點(diǎn)是ScriptSrc在動(dòng)態(tài)刪節(jié)點(diǎn)的時(shí)候潛在存在內(nèi)存泄露,據(jù)稱(chēng)不太好解決。而更大的問(wèn)題是如果傳輸json的話,必須用var json = {property:value};這樣的形勢(shì),對(duì)json有了污染,不太好。
所以,對(duì)于io的方案主要來(lái)說(shuō)幾個(gè)點(diǎn):
a、跨域:ScriptSrc完全跨域,iFrame跨子域,XmlHttp不跨域。
b、文件:傳文件iframe比較簡(jiǎn)單,ScriptSrc和XmlHttp都需要客戶(hù)端切分get上去,比較麻煩。
c、生命周期控制:XmlHttp比較完善,而iFrame可以模擬但是不完全,ScriptSrc則更難。
需要代碼請(qǐng)去JavaEye下載:D
http://www.javaeye.com/topic/82169
That's all。本來(lái)想No fluff just staff的寫(xiě)這篇東西,但是無(wú)奈嘴碎寫(xiě)的優(yōu)點(diǎn)亂,請(qǐng)大家見(jiàn)諒。關(guān)于本問(wèn)題的解決希望大家多提意見(jiàn),互相交流。