js異步從入門到放棄(四)- Generator 封裝異步任務

在以前的文章介紹了傳統異步的實現方案,本文將介紹ES6中的一種全新的異步方案--Generator函數。html

generator簡介

簡單介紹一下generator的原理和語法,(更詳細內容請看ECMAScript 6 入門,本文只介紹和異步相關的核心內容)es6

基本語法

經過一個簡單的例子來了解generator函數異步

function* MyGenerator() {
    yield 'yield的含義是:執行此處時,暫停執行當前函數'
    yield '暫停以後的函數能夠用next方法繼續執行'
    return '遇到return以後會真正結束,done會變成true'
}

const gen = MyGenerator() //執行後返回的是一個指針,能夠利用該指針執行next方法
console.log(gen.next()) // {value: "yield的含義是:執行此處時,暫停執行當前函數「, done: false}
console.log(gen.next())// {value: "暫停以後的函數能夠用next方法繼續執行", done: false}
console.log(gen.next())// {value: "遇到return以後會真正結束,done會變成true", done: true}

數據交互

數據交互指的是能夠經過yield把當前執行的結果傳出,也可使用next把外部參數傳入async

function* MyGenerator(){
        const result = yield '傳出的數據'
        return result
    }
    const gen = MyGenerator()
    console.log(gen.next())// generatorAndAsync2.html:19 {value: "傳出的數據", done: false}
    console.log(gen.next('傳入的數據'))// {value: "傳入的數據", done: true}

交互的參數類型固然也能夠是函數函數

function sayHi(){
        console.log('hi');
    }
    
    function* MyGenerator(){
        const result = yield sayHi()
        return 
    }
    
    const gen = MyGenerator()
    const result = gen.next()// {value: sayHi, done: false}
    result.value() // 正常輸出'hi'

具有以上最核心的兩個特性以後,generator就能夠進行異步操做封裝。測試

異步任務封裝

首先,結合異步任務的特色以及前文提到的genrator函數的特性,提煉出使用generator封裝異步操做的核心思路:prototype

  1. 在異步任務執行時,使用yield交出執行權
  2. 在異步任務結束後,使用next交還執行權

起步

從一個最簡單的例子開始:指針

// 1. 首先寫一個異步任務,在一秒後返回特定字符串
function asyncTask(callback){
    setTimeout(()=>{
        callback('Hello Leo')
    }, 1000)
}

// 2. 接下來寫出指望執行的順序
function* runTask() {
    let text = yield asyncTask
    console.log(text) // 咱們指望這裏正常輸出Hello Leo
}
// 3. 按照指望值執行函數
const gen = runTask()// 此時執行權已經交出
gen.next().value(function (text) {// 執行asyncTask並傳入callback ,關鍵點在於在callback裏調用next交還執行權
    gen.next(text)
})

首先,這段代碼雖然很粗糙,可是已經反映了使用generator封裝異步任務的核心思想。最直觀的受益就是:runTask的內容看起來很像同步代碼,條理清晰,很適合閱讀。code

可是上面第3部分關於執行的代碼很不靈活,咱們不能每次都這麼寫一段,所以接下來的目標就是實現一個任務執行器htm

自動任務執行器

一樣的,先思考覈心的思路:要想讓某個generator函數自動執行,無非就是一個while循環:

1. 若是當前yield返回值的done屬性爲true,說明任務已經執行完成;
2. 若是當前yield返回值的done屬性爲false,說明任務還未執行完成,則繼續執行next,同時要注意參數傳遞

根據分析實現如下的執行器:

function autoExecute(task) {
    const gen = task()
    let result = gen.next()
    while(true){
        if(result.done){
            break // 執行結束
            return 
        }
        console.log(result.value)//爲了便於觀察 咱們加上console.log
        result = gen.next(result.value) // 每次都應該重寫result 獲取最新結果
    }
}

function* simpleTask(){
    yield 1
    yield 2
    yield 3
    return 
}

autoExecute(simpleTask)// 測試咱們寫的自動執行器 可以正確輸出123

上面的執行器已經有了雛形,可是對於前面例子中,result.value爲函數的狀況尚未處理,所以還須要稍微補充:

function isFunction(source){
     return Object.prototype.toString.call(source) === "[object Function]"
}

function autoExecute(task) {
    const gen = task()
    let result = gen.next()
    let isRuningAsync = false // 因爲加入了異步處理,因此要增長一個標誌位避免重複進入循環體
    while (!isRuningAsync) {
        if (result.done) {
            return
        }
        console.log(result.value)

        /* start 補充的處理函數 */
        if (isFunction(result.value)) {
            isRuningAsync = true
            const callback = (arg) => {
                result = gen.next(arg) // 核心代碼
                isRuningAsync = false
            }
            result.value(callback)
            /* end 補充的處理函數 */
        } else {
            result = gen.next(result.value)
        }
    }
}
autoExecute(runTask) // 試着用這個自動執行器執行以前的異步任務

上面這個自動執行器,就完成了generator對異步任務的封裝。

總結

本文簡要介紹了generator函數的一些特性,重點在於說明如何使用generator函數對異步任務進行封裝,從而可以讓異步代碼編寫的更加清晰。

再次強調:用generator函數對異步任務封裝的思想是很明確的--控制 Generator 函數的流程,在適當的時機接收和交還程序的執行權,可是具體的實現方式並不惟一,例如本文用的是最簡單直接的回調函數方式,在阮一峯老師的《es6入門》教程裏,也有使用thunk思路來說解的部分,能夠自行查閱。

相關文章
相關標籤/搜索