這篇文章主要介紹了JS下大批量異步任務(wù)按順序執(zhí)行的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括來(lái)鳳網(wǎng)站建設(shè)、來(lái)鳳網(wǎng)站制作、來(lái)鳳網(wǎng)頁(yè)制作以及來(lái)鳳網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,來(lái)鳳網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到來(lái)鳳省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!
前言
最近需要做一個(gè)瀏覽器的, 支持大體積文件上傳且要支持?jǐn)帱c(diǎn)續(xù)傳的上傳組件, 本來(lái)以為很容易的事情, 結(jié)果碰到了一個(gè)有意思的問(wèn)題:
循環(huán)執(zhí)行連續(xù)的異步任務(wù), 且后一個(gè)任務(wù)需要等待前一個(gè)任務(wù)的執(zhí)行狀態(tài)
這么說(shuō)可能有點(diǎn)空泛, 以我做的組件舉例:
這個(gè)組件本意是為了上傳大體積視頻, 和支持?jǐn)帱c(diǎn)續(xù)傳, 因?yàn)閯?dòng)輒幾個(gè)G的視頻不可能直接把文件讀進(jìn)內(nèi)存, 只能分片發(fā)送(考慮到實(shí)際網(wǎng)絡(luò)狀態(tài), 每次發(fā)送大小定在了4MB), 而且這么做也符合斷點(diǎn)續(xù)傳的思路.
組件工作流程如下:
選定上傳文件后, 從H5原生upload組件里取得文件的blob對(duì)象 (同步)
通過(guò)blob對(duì)象的slice方法把文件切片 (同步)
新建一個(gè)Filereader對(duì)象, 通過(guò)Filereader的readAsArrayBuffer方法讀取步驟2中生成的slice (異步)
如果步驟3的buffer讀取成功(通過(guò)監(jiān)控Filereader的onload事件), 則ajax發(fā)送步驟3中的buffer (異步)
如果ajax發(fā)送成功, 且服務(wù)器儲(chǔ)存完成, 會(huì)向客戶(hù)端發(fā)回一個(gè)成功狀態(tài)碼, 如果ajax的response中存在這個(gè)狀態(tài)碼, 則進(jìn)行下一次切片發(fā)送 (異步)
從組件工作流程可以發(fā)現(xiàn), 3,4,5中的連續(xù)異步任務(wù), 必須要按順序進(jìn)行, 且每一步任務(wù)間存在相互依賴(lài), 最后還要對(duì)這些步驟進(jìn)行多次循環(huán).
如果只是處理單次的連續(xù)異步任務(wù), 通過(guò)promise鏈?zhǔn)秸{(diào)用即可, 但是要循環(huán)執(zhí)行這樣的連續(xù)異步任務(wù)讓我想了很久.
后來(lái)google了很久也沒(méi)發(fā)現(xiàn)解決方案, 無(wú)奈下閉門(mén)造車(chē)了2天, 想出了3套方案, 權(quán)當(dāng)拋磚引玉, 希望各位給出更好建議
3套方案的核心思想相同, 類(lèi)似觀(guān)察者模式, 來(lái)控制循環(huán)的進(jìn)行, 區(qū)別在于循環(huán)的實(shí)現(xiàn)不同, 實(shí)際上這3套方案也是我自我否定的過(guò)程, 不斷思考更好的方法, 整個(gè)組件代碼略長(zhǎng), 在此只挑出問(wèn)題相關(guān)部分, 且省略錯(cuò)誤處理部分
方案1
依然以上傳組件舉例
//循環(huán)狀態(tài)標(biāo)記,0為初始狀態(tài),1為正常,2為出錯(cuò) let status = 0; /* 新建Filereader,讀取文件切片,返回一個(gè)promise * 把讀取成功的arraybuffer通過(guò)reslove傳出 */ const createReader = ()=> { return new Promise ((reslove, reject)=> { let reader = new Filereader(); ... reader.onload = ()=> { reslove(reader.result) } reader.onerror = ()=> reject() }) } // ajax發(fā)送createReader方法讀取到的Buff const createXhr = ()=> { const xhr= new XMLHttpRequest(); return new Promise ((reslove, reject)=> { ... xhr.onreadystatechange= ()=> { ... //如果readyState == 4,status == 200且服務(wù)器的狀態(tài)碼存在,更改全局標(biāo)記為1 status = 1; reslove() } }) } //每一輪循環(huán)開(kāi)始前都檢查一次全局狀態(tài)標(biāo)記 const checkStatus = ()=> { ... if (status == 1) { loop() } } //循環(huán)過(guò)程的鏈?zhǔn)秸{(diào)用 const loop = ()=> { createReader().then(()=> createXhr()).then(()=> checkStatus()); }
方案1是基于初見(jiàn)問(wèn)題的'想當(dāng)然'解決方法, 碰到異步任務(wù)就promise, 這樣的循環(huán)長(zhǎng)鏈調(diào)用, 寫(xiě)法不優(yōu)雅, 且錯(cuò)誤調(diào)試異常麻煩, 更爆炸的是因?yàn)殚]包問(wèn)題, 在循環(huán)執(zhí)行中這些內(nèi)存難以回收, 內(nèi)存消耗急劇增加, 只能等待循環(huán)執(zhí)行完成
方案2
徹底引入觀(guān)察者模式, 構(gòu)造一個(gè)簡(jiǎn)單的EventEmitter, 通過(guò)event.on, event.emit的形式完成循環(huán)
//模仿node.js的EventEmitter class EventEmitter { constructor() { this.handler = {}; } on(eventName, callback) { if (!this.handles){ this.handles = {}; } if (!this.handles[eventName]) { this.handles[eventName] = []; } this.handles[eventName].push(callback); } emit(eventName,...arg) { if (this.handles[eventName]) { for (var i=0;i{ let reader = new Filereader(); ... reader.onload = ()=> { ev.emit('toajax') } }) //監(jiān)聽(tīng)toajax事件,如果上傳成功,就觸發(fā)createReader事件開(kāi)始讀取下一切片 ev.on('toajax', ()=> { let xhr= new XMLHttpRequest(); ... xhr.onreadystatechange = ()=> { //如果readyState == 4,status == 200且服務(wù)器的狀態(tài)碼存在 ev.emit('createReader') } })
方案2徹底貫徹'事件', 代碼語(yǔ)義更自然, 錯(cuò)誤調(diào)試也比方案1更為簡(jiǎn)單, 但內(nèi)存泄漏問(wèn)題依然存在
方案3
方案3, 回歸方案1的狀態(tài)管理方式, 但是通過(guò)setInterval方法來(lái)實(shí)現(xiàn)循環(huán).
//全局狀態(tài)標(biāo)記 let status = 0; //讀取切片 const createReader = ()=> { let reader = new Filereader(); ... reader.onload = ()=>status = 1 } //上傳切片 const createXhr = ()=> { let xhr= new XMLHttpRequest(); ... xhr.onreadystatechange = ()=> { ... //如果readyState == 4,status == 200且服務(wù)器的狀態(tài)碼存在 status = 2 } } /* 設(shè)置一個(gè)間隔時(shí)間極短的計(jì)時(shí)器,根據(jù)status決定下一步的任務(wù), * 上傳完成后定時(shí)器自動(dòng)清除自己 * 另外有判斷文件是否上傳完成的方法,這里就不寫(xiě)了 */ let timer = setInterval(()=> { if (status == 2) { createReader(); } else if (status == 1) { createXhr(); } else if (status == 3) { clearInterval(timer); } },10)
不可否認(rèn), 方案3看上去很low, 如果追求極致的執(zhí)行效率, 方案3無(wú)疑是最蠢的辦法, 但是方案三相當(dāng)于把異步任務(wù)轉(zhuǎn)化為了同步任務(wù), 語(yǔ)義簡(jiǎn)潔, 且沒(méi)有上面2種方法的內(nèi)存泄漏問(wèn)題.
方案3本質(zhì)上是把while (true)改寫(xiě)成了setInterval, 因?yàn)閣hile true會(huì)阻塞線(xiàn)程, 各種異步事件的回調(diào)也會(huì)被一同阻塞, 所以選擇了setInterval
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“JS下大批量異步任務(wù)按順序執(zhí)行的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!