一.基礎知識
Generator函數是ES6出現的一種異步操作實現方案。
異步即代碼分兩段,但是不是連續執行,第一段執行完后,去執行其他代碼,等條件允許,再執行第二段。
同步即代碼連續執行。
1. Generator函數是什么?
Generator函數是一種遍歷器生成函數;運行后返回一個遍歷器對象。
函數內部可以看作一個狀態機;可以通過遍歷器的next方法遍歷函數內部的各個狀態。
Generator函數不是構造函數!不能使用new命令生成實例。否則報錯!
但是生成的實例是Generator函數的實例對象。
function* testGen() { yield 1; } const gen = testGen(); console.log(gen instanceof testGen); //生成的對象是Generator的實例 console.log(gen.__proto__ === testGen.prototype); // 生成的實例繼承原型上的屬性和方法 // 運行結果 true
true
注意:
function* testGen() { yield 1; yield 2; } testGen().next(); // {value:1, done:false} testGen().next(); // {value:1, done:false} // 上面沒有連續執行是因為,每次調用testGen方法都生成一個新的遍歷器;如果想連續執行;可以將其賦值給一個變量,操作變量
構造函數本質是通過new命令,返回this對象,即實例對象。
但是Generator函數不返回this,它返回遍歷器。通過直接執行返回。
此時函數內部的this是函數執行時所在的對象,即window(非嚴格模式,嚴格模式下是undefined)
<script type="module"> function* testGen() { console.log(this); } const gen = testGen(); gen.next(); </script> // 運行結果如下(嚴格模式) undefined
如果想要生成的遍歷器對象可以訪問this的屬性和方法,可以使用call或者apply方法綁定this到其原型對象上。
function* testGen() { this.a = 5; return this; } const gen = testGen.call(testGen.prototype); const thisObj = gen.next().value;// gen.next().value === testGen.prototype console.log(thisObj.a); // 5
2. Generator函數聲明
1.作為函數聲明
function* fnName() { // function和函數名稱之間有一個*, 通常緊跟function后 yield 表達式; // 函數內部使用yield命令 return val; //表示最后一個狀態 } const myGenerator = fnName(); //生成遍歷器對象;相當於指向內部狀態的指針對象
其中,*是Generator函數的標識;yield(“產生”)是狀態生成命令。
2. Generator函數作為對象的屬性聲明
const obj = { *testGen() { ... } } // 相當於 const obj = { testGen: function* () { ... } }
3. Generator函數生成遍歷器對象
調用Generator函數后,會返回一個遍歷器對象;但是此時函數內部代碼並不執行。
遍歷器對象可以通過自身的next方法訪問Generator函數內部的狀態。
yield命令除了表示生成狀態,還表示暫停標識,即next方法遇到yield命令即停止運行。
所以next()方法表示從代碼起始或者上一次停止的位置運行到下一個yield或者return或者代碼結束。
next方法返回結果格式如下:
{value: 值, done: true/false} // value是當次運行的狀態,即yield命令后面的表達式的值 // done表示是否遍歷結束
想繼續執行就要繼續調用next方法,如果函數內部沒有return,則一直運行到代碼結束;
返回結果最后是:
{value: undefined, done: true} //繼續調用next方法,會一直返回該值
如果遇到return value,則return語句即表示遍歷結束。
返回結果是:
{value: value, done: true} //繼續調用next方法,返回 {value: undefined, done: true} // 繼續調用next,一直返回該值
示例:
function* testGen() { yield 1; return 2; } const gen = testGen(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); // 運行結果如下 {value: 1, done: false} {value: 2, done: true} {value: undefined, done: true} {value: undefined, done: true}
✅可以遍歷遍歷器狀態的遍歷方法有

function* testGen() { yield 1; yield 2; yield 3; } const gen = testGen(); /****1. 通過擴展運算符...*******/ let result1 = [...gen]; console.log(result1) /****2. 通過for...of遍歷*******/ let result2 = []; for(let item of gen) { result2.push(item); } console.log(result2) /****3. 通過數組解構賦值********/ let [...result3] = gen; console.log(result3) /****4. 通過Array.from********/ let result4 = Array.from(gen); console.log(result4) // 運行結果如下: [1,2,3] // 遍歷器已經遍歷結束,繼續遍歷后面會全部是[] [] [] []

function* testGen() { yield 1; yield 2; yield 3; } const gen1 = testGen(); /****1. 通過擴展運算符...*******/ let result1 = [...gen1]; console.log(result1) /****2. 通過for...of遍歷*******/ const gen2 = testGen(); let result2 = []; for(let item of gen2) { result2.push(item); } console.log(result2) /****3. 通過數組解構賦值********/ const gen3 = testGen(); let [...result3] = gen3; console.log(result3) /****4. 通過Array.from********/ const gen4 = testGen(); let result4 = Array.from(gen4); console.log(result4) // 運行結果如下: [1,2,3] [1,2,3] [1,2,3] [1,2,3]
4. yield表達式
1. Generator函數中yield表達式可以不存在
此時Generator函數是一個等待next方法啟動的暫緩執行函數。
function* testGen() { console.log('等待執行'); } const gen = testGen(); setTimeout(() => { gen.next(); // 2秒后打印'等待執行' }, 2000)
2. yield表達式最近的函數只能是Generator函數,否則聲明時就會報錯
function* testGen() { [1,2].forEach(function(item) { //回調函數不是Generator函數;可以改為for循環 yield item; //Uncaught SyntaxError: Unexpected identifier }) }
3. yield表達式不是單獨使用或者不是在=右側時,需要將其括起來
function* testGen() { console.log(1 + (yield 1)); }
4. yield表達式本身不返回任何值,或者說返回undefined
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } let gen = testGen(); gen.next(); gen.next(); gen.next(); //遍歷結束 // 運行結果如下 undefined undefined
5. 帶參數的next方法
gen.next(value);
帶參數的next方法,其中的參數表示上一次yield表達式的返回值。
所以第一次next方法如果有參數,參數無效,因為第一個next從代碼起始執行,之前沒有yield表達式。
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } let gen = testGen(); gen.next('是啥都沒有用'); gen.next('give1'); //第一個yield返回give1 gen.next('give2'); //第二個yield返回give2 // 運行結果如下 give1 give2
如果想要第一個next方法的參數起作用,可以將Generator函數包裝一下
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } function wrapper(fn) { let gen = fn(); gen.next(); return gen; } let gen = wrapper(testGen); gen.next('give1'); //第一個yield返回give1 gen.next('give2'); //第二個yield返回give2 // 運行結果如下 give1 give2
上面的示例也說明,Generator函數的執行進度可以保留。
二.Generator實例方法
1. Generator.prototype.throw()
方法的作用從遍歷器對象上手動拋出異常。
1. 在Generator函數外部,從遍歷器對象上拋出異常。
該異常可以被Generator函數內部的try...catch捕獲;前提是至少執行一次next方法;
拋出異常的位置位於遍歷器當前狀態指針所在的位置。

function* testGen() { try{ yield; } catch(err) { console.log('innerErr-->',err); } yield 1; } const gen = testGen(); gen.next(); gen.throw(new Error("gen.throw")); // 在第二個yield位置拋出異常,無法捕獲 console.log('after'); // 運行結果 innerErr-->Error: gen.throw after

function* testGen() { try{ yield; } catch(err) { console.log('innerErr-->',err); } yield 1; } const gen = testGen(); gen.next(); gen.next(); // 比上面的示例多了一個next方法 gen.throw(new Error("gen.throw")); // 在第二個yield位置拋出異常,無法捕獲 console.log('after'); // 運行結果 Uncaught Error: gen.throw //后面的不再執行
也可以被外部調用該方法的位置的catch捕獲。

function* testGen() { yield; } const gen = testGen(); gen.next(); try { gen.throw(new Error("gen.throw")); } catch (error) { console.log('outer-->',error); } console.log('after'); // 運行結果 outer-->Error: gen.throw after
當內部和外部try...catch同時存在時,錯誤先被Generator函數內部捕獲;

function* testGen() { try { yield; } catch (error) { console.log('inner-->',error) } } const gen = testGen(); gen.next(); try { gen.throw(new Error("gen.throw")); } catch (error) { console.log('outer-->',error); } console.log('after'); // 運行結果 inner-->Error: gen.throw after
注意:try...catch相當於一次性錯誤捕獲器,捕獲一次錯誤,后面再有錯誤需要另外一個捕獲器。
而且catch方法(Generator內部的異常或者外部遍歷器對象throw的異常)會自動觸發一次next方法

function* testGen() { try { yield; // 相當於在此處拋出異常,被捕獲直接進入catch, catch執行完后繼續執行 console.log('yield after');// 不執行 } catch (error) { console.log('inner-->',error); //catch方法會自動觸發一次next方法 } console.log('inner catch after'); } const gen = testGen(); gen.next(); try { gen.throw(new Error("gen.throw")); // 被內部捕獲 gen.throw(new Error("gen.throw1")); // 被外部捕獲,直接進入catch gen.throw(new Error("gen.throw2")); // 不執行 console.log('gen.throw after'); // 不執行 } catch (error) { console.log('outer-->',error); } console.log('after'); // 運行結果 inner-->Error:gen.throw inner catch after outer-->Error:gen.throw1 after
2. 在Generator函數外部,用全局throw一個異常,只能在外部捕獲異常 。
外部捕獲全局throw的錯誤的catch方法不會觸發next方法。

function* testGen() { try { yield; } catch (error) { console.log('inner-->',error); } } const gen = testGen(); gen.next(); try { throw new Error("global throw"); } catch (error) { console.log('outer-->',error); } console.log('after'); // 運行結果 outer-->Error:global throw after
3. 在Generator函數內部拋出異常,既可以被內部捕獲,也可以被外部捕獲

function* testGen() { try { throw new Error("內部拋出異常1"); } catch (error) { console.log('inner-->',error); } throw new Error("內部拋出異常2"); } const gen = testGen(); try { gen.next(); } catch (error) { console.log('outer-->',error); } // 運行結果如下: inner-->Error:內部拋出異常1 outer-->Error:內部拋出異常2
4. 在Generator函數內部拋出異常,如果只在外部捕獲,那么函數內部后續的代碼不再執行;
js引擎認為遍歷器已經遍歷結束,再調用next方法會返回{value: undefined, done: false}

function* testGen() { throw new Error("內部拋出異常1"); console.log('after'); //不執行 } const gen = testGen(); try { gen.next(); } catch (error) { console.log('outer-->',error); } // 運行結果如下: outer-->Error:內部拋出異常1
如果在內部捕獲,捕獲后,catch后的代碼會執行到下一個yield或者結束。

function* testGen() { try{ throw new Error("內部拋出異常1"); } catch(error) { console.log('inner-->',error) } console.log('after'); //不執行 } const gen = testGen(); gen.next() // 運行結果如下: inner-->Error:內部拋出異常1 after
2. Generator.prototype.return()
方法返回return給定的值,並終結遍歷器的遍歷。
function* testGen() { yield 1; yield 2; yield 3; } const gen = testGen(); gen.next(); // {value: 1, done: false} gen.return('end'); // {value: 'end', done: true} gen.next(); // {value: undefined, done: true}
如果調用return()方法的位置位於try模塊中,並且后面有finally模塊,那么調用return后立即執行finally模塊。
此時,如果finally代碼塊中有yield表達式,return方法相當於next方法, 然后將return語句放到finally代碼塊最后。
function* testGen() { try { console.log('--1--'); yield 1; console.log('--2--'); yield 2; console.log('--3--'); } finally { console.log('--finally start--'); yield 4; yield 5; console.log('--finally end--') } } const gen = testGen(); console.log(gen.next()); console.log(gen.return('end')); //{value: 4, done: false} console.log(gen.next()); //{value: 5, done: false} console.log(gen.next()); //{value: 'end', done: true} //運行結果如下 --1-- {value:1, done:false} --finally start-- {value: 4, done: false} {value: 5, done: false} ---finally end-- {value: 'end', done: true}
三.yield* 表達式
1. 語法
用於在Generator函數內部遍歷另一個Generator函數。
yield* 表明后面的表達式是個Generator函數,相當於在一個Generator函數內部for...of另一個遍歷器。
function* bar() { yield 1; yield 2; } function* foo() { yield 3; yield* bar(); /*相當於 for(let item of bar()) { yield item; }*/ yield 4; } const gen = foo(); console.log([...gen]); //[3,1,2,4]
yield表達式沒有返回值,但是yield*表達式有返回值。返回值的內容是 yield*后面的Generator函數的return的值。
function* bar() { yield 1; return 2; } function* foo() { yield 3; let result = yield* bar(); // yield*有返回值;如果bar沒有return,則返回值默認undefined console.log('result-->',result); yield 4; } const gen = foo(); console.log([...gen]); // 運行結果如下: result-->2 [3,1,4] //2是return的值,不能被for...of遍歷
2. 應用
1. 將多重數組平鋪
// Generator函數的遞歸實現 function* flat(arr) { for(let item of arr) { if (Array.isArray(item)) { yield* flat(item); } else { yield item; } } } let arr = [1,[2,[3,4]]]; const gen = flat(arr); console.log([...gen]);//[1,2,3,4]
2. 遍歷二叉樹,遍歷方式用Generator函數實現中序遍歷如下:
// 中序遍歷--升序--左->根->右 let inOrder = function* (node) { if (node) { yield* inOrder(node.left); yield node.data; yield* inOrder(node.right); } } let data = [56,28,78,20,35,68,88,10,25,30,45,80,89]; const bst = new BST(); data.forEach(i => bst.insert(i)) const result = [...bst.inOrder(bst.root)]; //[10, 20, 25, 28, 30, 35, 45, 56, 68, 78, 80, 88, 89]
四.應用
1. 給對象添加iterator接口-[Symbol.iterator]
諸如數組等可遍歷對象本質是本身含有[Symbol.iterator]屬性,該屬性是一個遍歷器生成方法。
使用for...of或者next()或者...進行遍歷時,本質上遍歷的是[Symbol.iterator]方法。
而Generator方法本身就是一個遍歷器生成方法。
所以可以通過將Generator函數賦值給目標對象,使其具有iterator接口。
示例:給不具有iterator接口的普通對象添加iterator接口

// 普通對象沒有iterator接口,所以不能使用for..of function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); return 3; } let obj = { a: 1 } obj[Symbol.iterator] = testGen; for (let item of obj) { // obj添加[Symbol.iterator]之前不能被遍歷 console.log(item); //item是內部的狀態值 } console.log([...obj]); // 運行結果如下 1 2 undefined undefined //for...of undefined undefined // ...遍歷 [1,2]
從上面可知:
for...of不需要手動遍歷,會自動遍歷生成器內部的所有狀態,但是不會遍歷done為true的值。
所以上面3不會被遍歷。
基礎語法:break對於for,while來說都是終止之后的循環;continue終止本次循環
示例: 實現fibonacci數列

function* fibonacci() { let [prev, curr] = [0, 1]; yield curr; while(true) { [prev, curr] = [curr, prev+curr]; yield curr; } } for(let item of fibonacci()) { if (item === 8) { break; } console.log(item); } // 運行結果如下: 1 1 2 3 5
示例:模擬Object.entries()方法

// Object.entries(obj)可以使用for...of遍歷 // 說明該方法返回遍歷器;寫一個方法覆蓋Object上的原有方法 Object.entries = function* (obj) { let objKeys = Object.keys(obj); for (let key of objKeys) { console.log('---selfdefined---'); yield [key, obj[key]]; } } let obj = {a:1,b:2}; for(let [key, value] of Object.entries(obj)) { console.log([key,value]) } // 運行結果如下: ---selfdefined--- ['a',1] ---selfdefined--- ['b',2]
2. Generator函數實現異步操作
yield后是Thunk函數或者Promise對象。
名詞解釋:
協程(coroutine): 相互協作的線程。幾個線程同時運行,但正在運行的只有一個。
代碼的執行權可以在不同的線程之間切換。
Generator函數可以交出函數的執行權(通過yield暫停)和取回(next方法)。
Thunk函數: 含有多個參數(其中含有一個回調函數)的函數轉為只接受一個回調函數作為參數的函數。
語法:
const Thunk = function(fn) { return function(...args) { return function(callback) { fn.call(this, ...args, callback) } } } //本質上是將所有的參數分為非callback參數和callback參數
Thunk函數使Generator函數可以處理異步任務。並且很方便實現自動執行器函數。

const Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); } }; }; const readFileThunk = Thunk(fs.readFile); function* readAsync() { const r1 = yield readFileThunk('/url1'); const r2 = yield readFileThunk('/url2'); const r3 = yield readFileThunk('/url3'); } //執行器 function run(fn) { const gen = fn(); function next(err, data) { //方法可以通過手動執行推導 const result = gen.next(data); if (result.done) return; result.value(next); // 上面的異步操作是同類型的操作,自動執行相當於相同的next方法一直回調 } next(); } run(readAsync);
除了自己實現自動執行函數,還可以通過“co”模塊實現Generator函數的自動執行。
前提: Generator函數中yield后面只能是Thunk函數或者Promise對象。
Thunk函數參照上面自己實現的run方法。
const co = require('co'); function* readAsync() { const r1 = yield readFileThunk('/url1'); const r2 = yield readFileThunk('/url2'); const r3 = yield readFileThunk('/url3'); } // 將Generator函數傳入co方法會自動執行 co(readAsync).then(() => { //co函數返回一個Promise對象 console.log('遍歷結束執行該語句'); });
當yield后面是Promise對象時
// yield后面是Promise的情況 function readFilePromise(fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, function(err, data) { if (err) reject(err); resolve(data); }) }) } function* readAsync() { const r1 = yield readFilePromise('/url1'); const r2 = yield readFilePromise('/url2'); const r3 = yield readFilePromise('/url3'); } //手動實現執行器--推導過程 const gen = readAsync(); gen.next().value.then(data => { //可以發現回調函數的內容是類似的 gen.next(data).value.then(data => { gen.next(data); }) }) //根據上面的示例自己實現執行器 function run(fn) { const gen = fn(); function next(data) { const result = gen.next(data); if (result.done) return result.value; // 如果yield后面是Promise對象,run(fn)的返回值是return的值 result.then((data) => { //等待異步操作執行完成 next(data); }); } next(); } run(readAsync);
co方法的原理: 1.判斷參數是否是Generator 2. 返回一個Promise對象 3. 通過then遞歸 4. yield后面不是Promise或者Thunk執行Reject

function co(gen) { var ctx = this; return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.call(ctx); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } }); } function next(ret) { if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); return onRejected( new TypeError( 'You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"' ) ); }
co模塊支持並發的異步操作: 幾個異步操作同時執行, 將其放入yield后的數組或者對象中,等全部執行完,才執行下一步。
// 注意: 要求在co模塊中yield才能用[]或者{} const co = require('co'); // 數組 co(function* gen() { const res = yield [ // 返回一個數組 Promise.resolve(1), Promise.resolve(2) ]; console.log(res); // [1, 2] }).catch(() => {}) // 對象 co(function* gen() { const res = yield { 0: Promise.resolve(1), 1: Promise.resolve(2) } console.log(res); // { '0': 1, '1': 2 } }).catch(() => {}) console.log('end'); // 運行結果如下: end [1,2] {'0':1, '1':2} //說明co方法是異步方法
co模塊並發的應用: 處理Node中Stream事件
const co = require('co'); const fs = require('fs'); const txtPath = path.join(__dirname, '1.txt'); const readStream = fs.createReadStream(txtPath); let toWordCount = 0; function* countWord() { while(true) { const result = yield Promise.race([ new Promise((resolve, reject) => readStream.once('data',resolve)), new Promise((resolve, reject) => readStream.once('end',resolve)), new Promise((resolve, reject) => readStream.once('error',reject)), ]); if (!result) { // data事件觸發的時候result有效<Buffer....> break; } console.log('result-->',result.toString()); //toString方法將二進制轉為字符串 readStream.removeAllListeners('data'); readStream.removeAllListeners('end'); readStream.removeAllListeners('error'); toWordCount+=(result.toString().match(/to/ig) || []).length; } console.log('count-->',toWordCount); } co(countWord); // 運行結果如下 result--> You cannot look back to the long period of our private friendship and political harmony with more affecting recollections than I do. If they are a source of pleasure to you, what aren’t they not to be to me? We cannot be deprived of the happy consciousness of the pure devotion to the public good with Which we discharge the trust committed to us and I indulge a confidence that sufficient evidence will find in its way to another generation to ensure, after we are gone count--> 8