以前寫過一個分批預加載資源的插件,其實質即是串行執行異步,使用的方法是generator + promise ~~
前幾天寫了一個爬蟲,抓取頁面的n個頁面的音頻資源,其也是串行執行異步,可是在使用的async/await + promise,這裏對兩個方法作一下對比,會發現async/await將使得代碼更爲簡潔。node
預加載的思路以下:
a、線性(串行)的控制每批次的資源加載,完成一批,再裝載另外一批的處理;(按數組順序實現每批次的加載)
b、實現單批資源的異步下載;(單批次內的資源異步下載,無需順序)
c、實現單個資源的下載。編程
代碼以下:數組
/* Created by hity on 06/08/17 參數說明: auto: 是否自執行 imgs: 需預加載的圖片列表,爲二維表 ignore:在自執行過程當中,須要跳過的圖片set 腳標 firstSetReady: 第一組圖片完成加載之後,置爲true,便於外部掌握狀態 說明: 非自執行的需求,可直接調用loadOneSetImages方法,返回值爲promise */ //co爲generator的線性處理函數(自執行),實質是g.next()執行權移交,yield後只能使用thunk 或者 promise import co from 'co' class Preload { constructor(auto, imgs = [], ignore = []) { this.imgs = imgs this.ignore = ignore this.auto = auto this.firstSetReady = false this.finished = false this.init() } init() { if (this.auto) { co(this.autoExeImageStream()).then((data) => { console.log('資源加載完畢~') this.finished = true }).catch(() => { console.log('資源加載出錯~') this.finished = true }) } } // generator,每一個yield處理一批資源 * autoExeImageStream() { for (let i = 0; i < this.imgs.length; i++) { if (this.ignore.indexOf(i) == -1) { yield this.loadOneSetImages(this.imgs[i]) } } } // 使用promise.all獲取每一個資源的加載結果 loadOneSetImages(imgList) { let promiseList = [] imgList.forEach((item) => { promiseList.push(this.loadSingleImage(item)) }) return Promise.all(promiseList).then((data) => { console.log('資源加載-1', data) this.firstSetReady = true }).catch(function() { this.firstSetReady = true }) } // 加載單個圖片資源 經過promise實現 loadSingleImage(src) { if (!src) { return new Promise((resolve, reject) => { resolve('noImage') }) } let newImg = new Image() newImg.src = src return new Promise((resolve, reject) => { newImg.onload = () => { resolve('success') } newImg.onerror = () => { resolve('fail') } }) } } export default Preload
如上代碼,思路b、c,每批次資源的異步加載,經過promise實現,其爲實現異步的根本,經過loadSingleImage函數,實現單個資源的加載;經過loadOneSetImages函數,實現單批資源的異步加載,在async/await的改造過程當中,這兩個部分不須要調整。promise
代碼的簡單實現以下:瀏覽器
init() { if (this.auto) { this.autoExeImageStream().then((data) => { console.log('資源加載完畢~') this.finished = true }).catch(() => { console.log('資源加載出錯~') this.finished = true }) } } async autoExeImageStream() { for (let i = 0; i < this.imgs.length; i++) { if (this.ignore.indexOf(i) == -1) { try { await this.loadOneSetImages(this.imgs[i]) } catch (error) { console.log(error) } } } }
如上,僅需改變init函數內的autoExeImageStream函數的調用方式,再也不依賴co模塊;autoExeImageStream爲異步,裏面的await會依次執行,從而達到串行(線性)的目的。其代碼量並未比使用co減小,但使用的缺失原生提供的能力,而不須要封裝的模塊。在觀察,發現代碼中在使用for循環(同generator實現代碼同),而非forEach等數組方法,這是由於forEach等數組方法的參數爲函數,將使得async or generator失效~~網絡
generator版本的資源預加載,使用封裝的co模塊,相對generator自己,其更接近async/await。併發
tips:async/await的實質是promise,調用async函數,返回的爲promise對象,因此在init中調用autoExeImageStream以後,直接可食用.then方法。異步
如此,有沒有更好的辦法來實現串行(線性)呢?async
優化代碼以下:異步編程
init() { if (this.auto) { this.autoExeImageStream() } } autoExeImageStream() { let seriesObj = [] this.imgs.forEach((item, index) => { if (this.ignore.indexOf(index) != -1) { return } seriesObj.push((done) => { this.loadOneSetImages(item).then(() => { done(null, index) }) }) }) async.series(seriesObj, (error, result) => { console.log('資源加載完成~', result) }) }
如上代碼,能夠看出init函數變得更簡潔,只需實現autoExeImageStream的調用;而該函數當中調用了async.series來實現串行化。其經過回調函數done來肯定當前函數是否結束,因此在處理異步時,須要將done函數放入異步回調的結尾處,從而找到當前任務結束的節點。
預加載方法的使用:
new Preload1(true, [ [ 'http://139.198.15.201:3000/images/channel_cover_1001_1506568590_adj02.jpg', 'http://139.198.15.201:3000/images/channel_cover_1007_1506577051_timg.jpg' ], [ 'http://139.198.15.201:3000/images/channel_cover_1008_1506579805_每週帶小學生共讀一本書.jpg', 'http://139.198.15.201:3000/images/channel_cover_1009_1506584962_科學隊長1.jpg' ], [ 'http://139.198.15.201:3000/images/channel_cover_1010_1506586126_世界童話故事.jpg' ] ])
上述三種代碼的執行結果相同,其結果以下:
圖一 console輸出
圖二 nextwork資源加載圖
總結:從(圖二 nextwork資源加載圖)能夠看出,圖片是按照資源二維數組的順序,按批次加載的。只有上一批次所有加載完畢,纔可進行下一批的加載,從而實現串行的異步加載。
幾個關鍵模塊或者方法總結:
promise:
全部異步處理的基礎,屬於microtask~在瀏覽器環境下,幾乎比全部的異步都要更早處理~常見的時間、網絡等異步,都屬於macrotask~但在nodejs環境當中,其級別低於Process.nextTick~~詳情可經過事件循環相關資料獲取;
async/await:
ES7的方法,其也是promise的封裝;其做用爲串行執行異步,在以事件循環爲核心的nodejs中,給開發者提供各類便利~~同時,其返回promise對象,但只返回resolve部分,因此在調用該方法時,若是返回結果不肯定的狀況下,需考慮使用try{}catch(){}捕獲,避免報錯;
generator:
其爲一個狀態機,使用next方法,控制移至下一個狀態,從而控制串行(相似手動移交狀態);
co:
其有兩種實現方式,thunk or promise~~而它是將generator進行的封裝,經過自動控制next方法,實現串行。因爲async/await的支持度已經很高,可考慮使用其替代該模塊。
async模塊使用詳情參考:nodejs之async異步編程