JavaScript 中的生成器有什么用?
今天我們要講的是JavaScript中一個(gè)不太常用的Generator語(yǔ)法。我很少看到有人在實(shí)際項(xiàng)目開(kāi)發(fā)中使用它。
可能是因?yàn)樗恼Z(yǔ)法比較復(fù)雜,而且是 async/awiatcan ,所以人們很少使用它。然而,Generatorit 仍然是。
今天我們就從基礎(chǔ)開(kāi)始練習(xí)Generator。
Generator介紹
JavaScript GeneratorE6是引入的一種新型函數(shù),可以生成多個(gè)值序列,可以暫停和恢復(fù)執(zhí)行,使我們能夠更簡(jiǎn)單、高效地實(shí)現(xiàn)迭代器。
如果我們看到一個(gè)函數(shù)后面跟著一個(gè) * 符號(hào),那么它就是一個(gè) Generatorfunction :
function* myGenerator() {
// Generator function
}
GeneratorFunctions 可以使用yield語(yǔ)句來(lái)定義要生成的值的序列。
每當(dāng)yield語(yǔ)句時(shí),Generator函數(shù)就會(huì)暫停執(zhí)行并返回一個(gè)包含當(dāng)前生成值的對(duì)象,然后執(zhí)行流程將暫停,直到下一次調(diào)用generator函數(shù)。
它的返回值是一個(gè)迭代器,可以通過(guò)調(diào)用 next() 方法來(lái)獲取下一個(gè)生成的值。Generator函數(shù)中的所有yield語(yǔ)句都執(zhí)行完畢后,done屬性為true,表示generator函數(shù)已經(jīng)結(jié)束(這里的流程描述比較抽象,后面用實(shí)際案例來(lái)解釋會(huì)更好)。
GeneratorFunctions 是 Python 首先從 Coroutine 語(yǔ)言中的 coroutine() 概念演變而來(lái),然后引入到 E6 標(biāo)準(zhǔn)中,希望用它來(lái)提高 JavaScript 中處理異步編程的能力。
Generator基本語(yǔ)法
GeneratorFunctions 使用 function*the 關(guān)鍵字定義,它可以包含多個(gè)yield表達(dá)式來(lái)控制函數(shù)執(zhí)行的流程:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
調(diào)用Generator函數(shù)并不執(zhí)行函數(shù)內(nèi)部的代碼,而是返回一個(gè)迭代器對(duì)象,next()通過(guò)調(diào)用這個(gè)對(duì)象的方法來(lái)執(zhí)行函數(shù)的代碼,并返回yield表達(dá)式返回的值:
const myGeneratorIterator = myGenerator();
console.log(myGeneratorIterator.next()); // print { value: 1, done: false }
console.log(myGeneratorIterator.next()); // print { value: 2, done: false }
console.log(myGeneratorIterator.next()); // print { value: 3, done: false }
console.log(myGeneratorIterator.next()); // print { value: undefined, done: true }
Generator函數(shù)執(zhí)行過(guò)程中,當(dāng)yieldan表達(dá)式時(shí),函數(shù)的執(zhí)行會(huì)被掛起,并將表達(dá)式的值返回給調(diào)用者。
當(dāng)next()方法時(shí),函數(shù)將從中斷處繼續(xù)執(zhí)行;我們可以在函數(shù)中指定返回一個(gè)最終返回值,該值將被包裝在包含要返回的屬性的完成對(duì)象中:
function* myGenerator() {
console.log('Start');
yield 1;
console.log('Middle');
yield 2;
console.log('End');
return 'Done';
}
const myGeneratorIterator = myGenerator();
console.log(myGeneratorIterator.next()); // print Start, { value: 1, done: false }
console.log(myGeneratorIterator.next()); // print Middle, { value: 2, done: false }
console.log(myGeneratorIterator.next()); // print End, { value: 'Done', done: true }
生成器的高級(jí)使用
yield*表達(dá)式
Yield* 允許 Generatorus 調(diào)用另一個(gè) Generator 函數(shù)或函數(shù)內(nèi)的可迭代對(duì)象。
當(dāng) Generatora 函數(shù)到達(dá)yield*表達(dá)式時(shí),它會(huì)暫停執(zhí)行并將執(zhí)行轉(zhuǎn)移到另一個(gè) Generator 函數(shù)或可迭代對(duì)象。
執(zhí)行權(quán)不會(huì)返回到原來(lái)的Generatorfunction。
function* foo() {
yield 1;
yield 2;
}
function* bar() {
yield* foo();
yield 3;
}
for (let value of bar()) {
console.log(value); // print 1, 2, 3
}
在這個(gè)例子中,表達(dá)式inGenerator函數(shù)調(diào)用該函數(shù)并將其迭代結(jié)果依次返回給該函數(shù)。bar()yield* foo()foo()bar()
數(shù)據(jù)交互
在Generator函數(shù)中,可以使用yield表達(dá)式將數(shù)據(jù)返回給調(diào)用者,調(diào)用者next()可以通過(guò)Generator方法將數(shù)據(jù)傳遞給函數(shù)。
這使得調(diào)用者和 Generator 函數(shù)之間能夠進(jìn)行數(shù)據(jù)交互。
function* foo() {
let x = yield;
yield x * 2;
}
let gen = foo();
gen.next(); // start generator
gen.next(10); // pass 10,print 20
在這個(gè)例子中,foo()函數(shù)的next()在第一次調(diào)用該方法時(shí)會(huì)停在第一個(gè)yield語(yǔ)句處,等待外部傳入的數(shù)據(jù)。
然后,當(dāng)next()方法時(shí)將從外部傳入的數(shù)據(jù)作為yield表達(dá)式的值,然后向下執(zhí)行,直到下一個(gè)yield表達(dá)式返回?cái)?shù)據(jù)。
實(shí)際用例
異步編程
GeneratorFunctions 還可以用于實(shí)現(xiàn)異步編程。可以通過(guò)調(diào)用next()方法和關(guān)鍵字:yieldPromise來(lái)控制函數(shù)的執(zhí)行狀態(tài)
function* myGenerator() {
const result1 = yield new Promise((resolve) => setTimeout(() => resolve('first'), 1000));
console.log(result1);
const result2 = yield new Promise((resolve) => setTimeout(() => resolve('second'), 2000));
console.log(result2);
const result3 = yield new Promise((resolve) => setTimeout(() => resolve('third'), 3000));
console.log(result3);
}
const generator = myGenerator();
const promise = generator.next().value;
promise.then((result) => generator.next(result).value)
.then((result) => generator.next(result).value)
.then((result) => generator.next(result).value);
看起來(lái)是不是和 async/awaitof 角色很相似,下面是兩種語(yǔ)法的一些比較:
優(yōu)勢(shì):
控制流程更靈活:可以使用 Generator 函數(shù)控制異步操作的執(zhí)行順序,多個(gè)異步操作可以按順序執(zhí)行,每個(gè)操作完成后執(zhí)行下一個(gè)操作,控制流程更靈活。
Generator函數(shù)的狀態(tài)可以復(fù)用:Generator函數(shù)的狀態(tài)可以保存在對(duì)象中,需要的時(shí)候函數(shù)可以繼續(xù)執(zhí)行,并且可以使用保存的狀態(tài)繼續(xù)異步操作。
更通用:GeneratorFunctions 可用于處理各種類(lèi)型的異步操作,包括事件、回調(diào)、迭代器和 Promisemore 。
缺點(diǎn):
更高的代碼復(fù)雜性:使用 Generator 函數(shù)可能會(huì)增加代碼的復(fù)雜性,因?yàn)樾枰~外的代碼和處理步驟。
可讀性差:對(duì)比async/await,Generator函數(shù)的語(yǔ)法和代碼結(jié)構(gòu)都比較復(fù)雜,可讀性不如async/await。
控制異步進(jìn)程
使用 Generatorfunctions 也非常方便。假設(shè)有一個(gè)需求場(chǎng)景API需要獲取,所有數(shù)據(jù)準(zhǔn)備好后進(jìn)行下一步。
此時(shí)可以使用Generator函數(shù)讓這個(gè)異步控制流程更加清晰:
function* fetchAllData() {
const data1 = yield fetch('api1');
const data2 = yield fetch('api2');
const data3 = yield fetch('api3');
return [data1, data2, data3];
}
function run(generator) {
const iterator = generator();
function handle(iteratorResult) {
if (iteratorResult.done) {
return Promise.resolve(iteratorResult.value);
}
return Promise.resolve(iteratorResult.value)
.then(res => handle(iterator.next(res)));
}
return handle(iterator.next());
}
run(fetchAllData).then(data => {
// handle all data
console.log(data);
});
處理大數(shù)據(jù)可以節(jié)省內(nèi)存
在處理大數(shù)據(jù)集時(shí),如果一次性將所有數(shù)據(jù)加載到內(nèi)存中,會(huì)造成內(nèi)存浪費(fèi)和程序性能下降。
Generator函數(shù)可以用來(lái)按需處理數(shù)據(jù),將數(shù)據(jù)一一讀取并轉(zhuǎn)換,減少內(nèi)存占用,提高程序性能。
function* dataGenerator() {
let index = 0;
while (true) {
yield index++;
}
}
function* processData(data, processFn) {
for (let item of data) {
yield processFn(item);
}
}
const data = dataGenerator();
const processedData = processData(data, item => item * 2);
for (let i = 0; i < 500; i++) {
console.log(processedData.next().value);
}
實(shí)現(xiàn)狀態(tài)機(jī)
GeneratorFunctions 也可用于實(shí)現(xiàn)狀態(tài)機(jī)。狀態(tài)機(jī)是由一組狀態(tài)和狀態(tài)之間的轉(zhuǎn)移規(guī)則組成的數(shù)學(xué)模型,可以用來(lái)描述系統(tǒng)的行為和狀態(tài)。
在實(shí)際開(kāi)發(fā)中,狀態(tài)機(jī)可以用來(lái)處理復(fù)雜的業(yè)務(wù)邏輯,比如表單驗(yàn)證、工作流控制等。
使用Generator函數(shù)實(shí)現(xiàn)狀態(tài)機(jī)的過(guò)程如下:
定義狀態(tài)機(jī)的各種狀態(tài),每個(gè)狀態(tài)對(duì)應(yīng)一個(gè) Generatorfunction 。
狀態(tài)之間的轉(zhuǎn)換是使用 Generatorfunction 的 statements.yield 實(shí)現(xiàn)的
調(diào)用Generator函數(shù)時(shí),使用循環(huán)依次執(zhí)行各個(gè)狀態(tài),直到狀態(tài)機(jī)完成。
這是 Generator 實(shí)現(xiàn)的示例:
function* stateMachine() {
let state = 'start';
while (true) {
switch (state) {
case 'start':
console.log('Enter start state');
state = yield 'start';
break;
case 'middle':
console.log('Enter middle state');
state = yield 'middle';
break;
case 'end':
console.log('Enter end state');
state = yield 'end';
break;
}
}
}
const sm = stateMachine();
console.log(sm.next().value); // Enter start state
console.log(sm.next('middle').value); // Enter middle state
console.log(sm.next('end').value); // Enter end state
最后
Generator與async/await相比,語(yǔ)法更加復(fù)雜,需要手動(dòng)控制執(zhí)行過(guò)程,使用起來(lái)相對(duì)麻煩。這也是我很少看到Generatorit被使用的原因之一。
這種語(yǔ)法實(shí)際上并不像看上去那樣簡(jiǎn)潔易懂。
但是在做一些復(fù)雜的控制流和狀態(tài)機(jī)處理的時(shí)候還是很有用的,Generator可以讓我們的流程更加清晰。