在以前的文章介紹了傳統異步的實現方案,本文將介紹ES6中的一種全新的異步方案--Generator函數。html
簡單介紹一下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
yield
交出執行權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思路來說解的部分,能夠自行查閱。