翻譯自html
githubgit
咱們能夠把generators理解成一段能夠暫停並從新開始執行的函數
es6
function* genFunc() { // (A) console.log('First'); yield; //(B) console.log('Second'); //(C) }
function*是定義generator函數的關鍵字,yield是一個操做符,generator 能夠經過yield暫停本身執行,另外,generator能夠經過yield接受輸入和對外輸入
github
當咱們調用genFunc(),咱們獲得一個generator對象genObj,咱們能夠經過這個genObj控制程序的執行算法
const genObj = genFunc()
上面的程序初始會暫停在行A
,調用genObj.next()會使程序繼續執行直到遇到下一個yieldjson
> genObj.next(); First { value: undefined, done: false }
這裏先忽略genObj.next()返回的對象,以後會介紹promise
如今,程序暫停在了行B,再次調用 genObj.next(),程序又開始執行,行C被執行app
> genObj.next() Second { value: undefined, done: true }
而後,函數就執行結束了,再次調用genObj.next()也不會有什麼效果了異步
generators 能夠扮演三種角色函數
每個yield能夠經過next()返回一個值,這意味着generators能夠經過循環或遞歸生產一系列的值,由於generator對象實現了Iterable接口,generator生產的一系列值能夠被ES6中任意支持可迭代對象的結構處理
,兩個例子,for of循環和擴展操做(...)
yield能夠經過next()接受一個值,這意味着generator變成了一個暫停執行的數據消費者直到經過next()給generator傳遞了一個新值
考慮到generators是能夠暫停的而且能夠同時做爲數據生產者和消費者,不會作太多的工做就能夠把generator轉變成協做程序(合做進行的多任務)
下面詳細介紹這三種
generators同時實現了接口Iterable 和 Iterator(以下所示),這意味着,generator函數返回的對象是一個迭代器也是一個可迭代的對象
interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
generator對象完整的接口後面會提到,這裏刪掉了接口Iterable的return()方法,由於這個方法這一小節用不到
generator函數經過yield生產一系列的值,這些值能夠經過迭代器的next()方法來使用,例以下面的generator函數生成了值a和b
function* genFunc(){ yield 'a' yield 'b' }
交互展現以下
> const genObj = genFunc(); > genObj.next() { value: 'a', done: false } > genObj.next() { value: 'b', done: false } > genObj.next() // done: true => end of sequence { value: undefined, done: true }
for (const x of genFunc()) { console.log(x); } // Output: // a // b
const arr = [...genFunc()]; // ['a', 'b']
> const [x, y] = genFunc(); > x 'a' > y 'b'
上面的generator函數沒有包含一個顯式的return,一個隱式的return 返回undefined,讓咱們試驗一個顯式返回return的generator
function* genFuncWithReturn() { yield 'a'; yield 'b'; return 'result'; }
下面的結構代表return 指定的值保存在最後一個next()返回的對象中
> const genObjWithReturn = genFuncWithReturn(); > genObjWithReturn.next() { value: 'a', done: false } > genObjWithReturn.next() { value: 'b', done: false } > genObjWithReturn.next() { value: 'result', done: true }
然而,大部分和可迭代對象一塊兒工做的結構會忽略done屬性是true的對象的value值
for (const x of genFuncWithReturn()) { console.log(x); } // Output: // a // b const arr = [...genFuncWithReturn()]; // ['a', 'b']
yield*會考慮done屬性爲true的value值,後面會介紹
若是一個異常離開了generator函數,next()能夠拋出它
function* genFunc() { throw new Error('Problem!'); } const genObj = genFunc(); genObj.next(); // Error: Problem!
這意味着next()能夠生產三種類型的值
咱們只能在generator函數中使用yield,若是咱們想經過generator實現遞歸算法,咱們就須要一種方式來在一個generator中調用另外一個generator,這就用到了yield*,如今,咱們只介紹yield*用在generator函數產生值的狀況,以後介紹yield*用在generator接受值的狀況
generator遞歸調用另外一個generator的方式
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; yield* foo(); yield 'y'; }
執行結構
const arr = [...bar()]; //['x', 'a', 'b', 'y']
在內部,yield*像下面這樣工做的
function* bar() { yield 'x'; for (const value of foo()) { yield value; } yield 'y'; }
另外,yield*的操做數不必定非得是一個generator函數生成的對象,能夠是任何可迭代的
function* bla() { yield 'sequence'; yield* ['of', 'yielded']; yield 'values'; } const arr = [...bla()]; // ['sequence', 'of', 'yielded', 'values']
yield*考慮可迭代對象的最後一個值
ES6中的不少結構會忽略generator函數返回的可迭代對象的最後一個值(例如 for of,擴展操做符,如上面介紹過的那樣),可是,yield*的結果是這個值
function* genFuncWithReturn() { yield 'a'; yield 'b'; return 'The result'; } function* logReturned(genObj) { const result = yield* genObj; console.log(result); // (A) }
執行結果
> [...logReturned(genFuncWithReturn())] The result [ 'a', 'b' ]
做爲數據的消費者,generator函數返回的對象也實現了接口Observer
interface Observer { next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
做爲observer,generator暫停執行直到它接受到輸入值
,這有三種類型的輸入,經過如下三種observer接口提供的方法
function* dataConsumer() { console.log('Started'); console.log(`1. ${yield}`); // (A) console.log(`2. ${yield}`); return 'result'; }
首先獲得generator對象
const genObj = dataConsumer();
而後執行genObj.next(),這會開始這個generator.執行到第一個yield處而後暫停。此時next()的結果是yield在行A產出的值(是undifined,由於這地方的yield後面沒有操做數
)
> genObj.next() //Started { value: undefined, done: false }
而後再調用next()兩次,第一次傳個參數'a',第二次傳參數'b'
> genObj.next('a') //1. a { value: undefined, done: false } > genObj.next('b') //2. b { value: 'result', done: true }
能夠看到,第一個next()調用的做用僅僅是開始這個generator,只是爲了後面的輸入作準備
能夠封裝一下
function coroutine(generatorFunction) { return function (...args) { const generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; }
使用
const wrapped = coroutine(function* () { console.log(`First input: ${yield}`); return 'DONE'; }); > wrapped().next('hello!') First input: hello!
generator對象有兩個另外的方法,return()和throw(),和next()相似
讓咱們回顧一下next()是怎麼工做的:
繼續執行到下一個yield,return或者throw:
return()和throw() 和next()相似工做,但在第二步有所不一樣
return()終止generator
return() 在 yield的位置執行return
function* genFunc1() { try { console.log('Started'); yield; // (A) } finally { console.log('Exiting'); } } > const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.return('Result') Exiting { value: 'Result', done: true }
阻止終止
咱們能夠阻止return()終止generator若是yield是在finally塊內(或者在finally中使用return語句)
function* genFunc2() { try { console.log('Started'); yield; } finally { yield 'Not done, yet!'; } }
這一次,return()沒有退出generator函數,固然,return()返回的對象的done屬性就是false
> const genObj2 = genFunc2(); > genObj2.next() Started { value: undefined, done: false } > genObj2.return('Result') { value: 'Not done, yet!', done: false }
能夠再執行一次next()
> genObj2.next() { value: 'Result', done: true }
發送一個錯誤
throw()在yield的位置拋一個異常
function* genFunc1() { try { console.log('Started'); yield; // (A) } catch (error) { console.log('Caught: ' + error); } }
> const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.throw(new Error('Problem!')) Caught: Error: Problem! { value: undefined, done: true }
到目前爲止,咱們只看到以yield的一個層面: 它傳播生成的值從被調用者到調用者。既然咱們如今對generator接受值感興趣,咱們就來看一下yield的另外一個層面:yield*能夠發送調用者接受的值給被調用者。在某種程度上,被調用者變成了活躍的generator,它能夠被調用者生成的對象控制
function* callee() { console.log('callee: ' + (yield)); } function* caller() { while (true) { yield* callee(); } }
> const callerObj = caller(); > callerObj.next() // start { value: undefined, done: false } > callerObj.next('a') callee: a { value: undefined, done: false } > callerObj.next('b') callee: b { value: undefined, done: false }
這一節介紹generator完整的接口(組合做爲數據生產者和消費者兩種角色)和一個同時要使用這兩種角色的使用場景:協同操做多任務
interface Generator { next(value? : any) : IteratorResult; throw(value? : any) : IteratorResult; return(value? : any) : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
接口Generator結合了咱們以前介紹過的兩個接口:輸出的Iterator和輸入的Observer
interface Iterator { // data producer next() : IteratorResult; return?(value? : any) : IteratorResult; } interface Observer { // data consumer next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
合做多任務是咱們須要generators同時處理輸入和輸出,在介紹generator是如何工做的以前,讓咱們先複習一下JavaScript當前的並行狀態
js是單線程的,但有兩種方式能夠消除這種限制
經過generators來簡化異步操做
一些基於Promise的庫經過generator來簡化了異步代碼,generators做爲Promise的客戶是很是理想的,由於它們能夠暫停直到結果返回
下面的例子代表co是如何工做的
co(function* () { try { const [croftStr, bondStr] = yield Promise.all([ // (A) getFile('http://localhost:8000/croft.json'), getFile('http://localhost:8000/bond.json'), ]); const croftJson = JSON.parse(croftStr); const bondJson = JSON.parse(bondStr); console.log(croftJson); console.log(bondJson); } catch (e) { console.log('Failure to read: ' + e); } });
注意這段代碼看起來是多麼的同步啊,雖然它在行A處執行了一個異步調用。
使用generators對co的一個簡單的實現
function co(genFunc) { const genObj = genFunc(); step(genObj.next()); function step({value,done}) { if (!done) { // A Promise was yielded value .then(result => { step(genObj.next(result)); // (A) }) .catch(error => { step(genObj.throw(error)); // (B) }); } } }
這裏忽略了next()(行A)和throw()(行B)能夠回拋異常
藉助上面的使用分析一下:
首先獲得generator對象
const genObj = genFunc();
而後將genObj.next()的返回值傳遞給step方法
step()中獲取到value和done,若是generator沒有執行完,當前的value就是上面使用中定義的promise
等到promise執行完,而後將結果result傳遞給generator函數
genObj.next(result) 而後在generator中程序繼續往下執行 const [croftStr, bondStr] = yield XXXX . . . .
注意行A處遞歸調用step(genObj.next(result)),使得generator函數中能夠存在多個異步調用,而co都能處理
整個過程多麼的巧妙啊。。。。。。。。。