async/await 與 generator、co 的對比

以前寫過一個分批預加載資源的插件,其實質即是串行執行異步,使用的方法是generator + promise ~~
前幾天寫了一個爬蟲,抓取頁面的n個頁面的音頻資源,其也是串行執行異步,可是在使用的async/await + promise,這裏對兩個方法作一下對比,會發現async/await將使得代碼更爲簡潔。node

預加載的思路以下:
a、線性(串行)的控制每批次的資源加載,完成一批,再裝載另外一批的處理;(按數組順序實現每批次的加載)
b、實現單批資源的異步下載;(單批次內的資源異步下載,無需順序)
c、實現單個資源的下載。編程

generator實現

代碼以下:數組

/*
    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

async/await

代碼的簡單實現以下:瀏覽器

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

async.series模塊

優化代碼以下:異步編程

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.series:async模塊(並不是ES7中所說的async/await),其做用在於流程控制,相應方法以下:
    • series(tasks,[callback]):串行執行;
    • waterfall(tasks,[callback]):瀑布流;其與series相似,不一樣之處在於參數的傳遞;
    • parallel(tasks,[callback]):是並行執行多個函數,每一個函數都是當即執行,不須要等待其它函數先執行;
    • parallelLimit(tasks, limit, [callback]) :與parallel相似,但可限制併發的函數數量;
    • whilst(test,fn,[callback]):至關於while循環,fn函數裏無論是同步仍是異步都會執行完上一次循環纔會執行下一次循環,對異步循環頗有幫助;
    • auto(tasks,[callback]):可串行、可並行~

    async模塊使用詳情參考:nodejs之async異步編程

相關文章
相關標籤/搜索