這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、介紹 Generator 函數是 ES6 提供的一種非同步編程解決方案,語法行為與傳統函數完全不同 回顧下上文提到的解決非同步的手段: 回調函數 promise 那麼,上文我們提到promsie已經是一種比較流行的解決非同步方案,那麼為什麼 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、介紹
Generator 函數是 ES6 提供的一種非同步編程解決方案,語法行為與傳統函數完全不同
回顧下上文提到的解決非同步的手段:
- 回調函數
- promise
那麼,上文我們提到promsie
已經是一種比較流行的解決非同步方案,那麼為什麼還出現Generator
?甚至async/await
呢?
該問題我們留在後面再進行分析,下麵先認識下Generator
Generator函數
執行 Generator
函數會返回一個遍歷器對象,可以依次遍歷 Generator
函數內部的每一個狀態
形式上,Generator
函數是一個普通函數,但是有兩個特征:
function
關鍵字與函數名之間有一個星號- 函數體內部使用
yield
表達式,定義不同的內部狀態
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; }
二、使用
Generator
函數會返回一個遍歷器對象,即具有Symbol.iterator
屬性,並且返回給自己
function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true
通過yield
關鍵字可以暫停generator
函數返回的遍歷器對象的狀態
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
上述存在三個狀態:hello
、world
、return
通過next
方法才會遍歷到下一個內部狀態,其運行邏輯如下:
- 遇到
yield
表達式,就暫停執行後面的操作,並將緊跟在yield
後面的那個表達式的值,作為返回的對象的value
屬性值。 - 下一次調用
next
方法時,再繼續往下執行,直到遇到下一個yield
表達式 - 如果沒有再遇到新的
yield
表達式,就一直運行到函數結束,直到return
語句為止,並將return
語句後面的表達式的值,作為返回的對象的value
屬性值。 - 如果該函數沒有
return
語句,則返回的對象的value
屬性值為undefined
hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
done
用來判斷是否存在下個狀態,value
對應狀態值
yield
表達式本身沒有返回值,或者說總是返回undefined
通過調用next
方法可以帶一個參數,該參數就會被當作上一個yield
表達式的返回值
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
正因為Generator
函數返回Iterator
對象,因此我們還可以通過for...of
進行遍歷
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
原生對象沒有遍歷介面,通過Generator
函數為它加上這個介面,就能使用for...of
進行遍歷了
function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } } let jane = { first: 'Jane', last: 'Doe' }; for (let [key, value] of objectEntries(jane)) { console.log(`${key}: ${value}`); } // first: Jane // last: Doe
三、非同步解決方案
回顧之前展開非同步解決的方案:
- 回調函數
- Promise 對象
- generator 函數
- async/await
這裡通過文件讀取案例,將幾種解決非同步的方案進行一個比較:
回調函數
所謂回調函數,就是把任務的第二段單獨寫在一個函數裡面,等到重新執行這個任務的時候,再調用這個函數
fs.readFile('/etc/fstab', function (err, data) { if (err) throw err; console.log(data); fs.readFile('/etc/shells', function (err, data) { if (err) throw err; console.log(data); }); });
readFile
函數的第三個參數,就是回調函數,等到操作系統返回了/etc/passwd
這個文件以後,回調函數才會執行
Promise
Promise
就是為瞭解決回調地獄而產生的,將回調函數的嵌套,改成鏈式調用
const fs = require('fs'); const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; readFile('/etc/fstab').then(data =>{ console.log(data) return readFile('/etc/shells') }).then(data => { console.log(data) })
這種鏈式操作形式,使非同步任務的兩段執行更清楚了,但是也存在了很明顯的問題,代碼變得冗雜了,語義化並不強
generator
yield
表達式可以暫停函數執行,next
方法用於恢復函數執行,這使得Generator
函數非常適合將非同步任務同步化
const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
async/await
將上面Generator
函數改成async/await
形式,更為簡潔,語義化更強了
const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
區別:
通過上述代碼進行分析,將promise
、Generator
、async/await
進行比較:
-
promise
和async/await
是專門用於處理非同步操作的 -
Generator
並不是為非同步而設計出來的,它還有其他功能(對象迭代、控制輸出、部署Interator
介面...) -
promise
編寫代碼相比Generator
、async
更為複雜化,且可讀性也稍差 -
Generator
、async
需要與promise
對象搭配處理非同步情況 -
async
實質是Generator
的語法糖,相當於會自動執行Generator
函數 -
async
使用上更為簡潔,將非同步代碼以同步的形式進行編寫,是處理非同步編程的最終方案
四、使用場景
Generator
是非同步解決的一種方案,最大特點則是將非同步操作同步化表達出來
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); // 載入UI loader.next() // 卸載UI loader.next()
包括redux-saga
中間件也充分利用了Generator
特性
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects' import Api from '...' function* fetchUser(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } function* mySaga() { yield takeEvery("USER_FETCH_REQUESTED", fetchUser); } function* mySaga() { yield takeLatest("USER_FETCH_REQUESTED", fetchUser); } export default mySaga;
還能利用Generator
函數,在對象上實現Iterator
介面
function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); } // foo 3 // bar 7
參考文獻
- https://es6.ruanyifeng.com/#docs/generator-async