JavaScript 中的生成器有什么用?

2023年 7月 24日 82.4k 0

今天我们要讲的是JavaScript中一个不太常用的Generator语法。我很少看到有人在实际项目开发中使用它。 

可能是因为它的语法比较复杂,而且是 async/awiatcan ,所以人们很少使用它。然而,Generatorit 仍然是。

今天我们就从基础开始练习Generator。

Generator介绍

JavaScript GeneratorE6是引入的一种新型函数,可以生成多个值序列,可以暂停和恢复执行,使我们能够更简单、高效地实现迭代器。

如果我们看到一个函数后面跟着一个 * 符号,那么它就是一个 Generatorfunction :

function* myGenerator() {
  // Generator function
}

GeneratorFunctions 可以使用yield语句来定义要生成的值的序列。 

每当yield语句时,Generator函数就会暂停执行并返回一个包含当前生成值的对象,然后执行流程将暂停,直到下一次调用generator函数。 

它的返回值是一个迭代器,可以通过调用 next() 方法来获取下一个生成的值。Generator函数中的所有yield语句都执行完毕后,done属性为true,表示generator函数已经结束(这里的流程描述比较抽象,后面用实际案例来解释会更好)。

GeneratorFunctions 是 Python 首先从 Coroutine 语言中的 coroutine() 概念演变而来,然后引入到 E6 标准中,希望用它来提高 JavaScript 中处理异步编程的能力。

Generator基本语法

GeneratorFunctions 使用 function*the 关键字定义,它可以包含多个yield表达式来控制函数执行的流程:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

调用Generator函数并不执行函数内部的代码,而是返回一个迭代器对象,next()通过调用这个对象的方法来执行函数的代码,并返回yield表达式返回的值:

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函数执行过程中,当yieldan表达式时,函数的执行会被挂起,并将表达式的值返回给调用者。 

当next()方法时,函数将从中断处继续执行;我们可以在函数中指定返回一个最终返回值,该值将被包装在包含要返回的属性的完成对象中:

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 }

生成器的高级使用

yield*表达式

Yield* 允许 Generatorus 调用另一个 Generator 函数或函数内的可迭代对象。

当 Generatora 函数到达yield*表达式时,它会暂停执行并将执行转移到另一个 Generator 函数或可迭代对象。 

执行权不会返回到原来的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
}

在这个例子中,表达式inGenerator函数调用该函数并将其迭代结果依次返回给该函数。bar()yield* foo()foo()bar()

数据交互

在Generator函数中,可以使用yield表达式将数据返回给调用者,调用者next()可以通过Generator方法将数据传递给函数。 

这使得调用者和 Generator 函数之间能够进行数据交互。

function* foo() {
  let x = yield;
  yield x * 2;
}


let gen = foo();
gen.next(); // start generator
gen.next(10); // pass 10,print 20

在这个例子中,foo()函数的next()在第一次调用该方法时会停在第一个yield语句处,等待外部传入的数据。 

然后,当next()方法时将从外部传入的数据作为yield表达式的值,然后向下执行,直到下一个yield表达式返回数据。

实际用例

异步编程

GeneratorFunctions 还可以用于实现异步编程。可以通过调用next()方法和关键字:yieldPromise来控制函数的执行状态

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);

看起来是不是和 async/awaitof 角色很相似,下面是两种语法的一些比较:

优势:

控制流程更灵活:可以使用 Generator 函数控制异步操作的执行顺序,多个异步操作可以按顺序执行,每个操作完成后执行下一个操作,控制流程更灵活。

Generator函数的状态可以复用:Generator函数的状态可以保存在对象中,需要的时候函数可以继续执行,并且可以使用保存的状态继续异步操作。

更通用:GeneratorFunctions 可用于处理各种类型的异步操作,包括事件、回调、迭代器和 Promisemore 。

缺点:

更高的代码复杂性:使用 Generator 函数可能会增加代码的复杂性,因为需要额外的代码和处理步骤。

可读性差:对比async/await,Generator函数的语法和代码结构都比较复杂,可读性不如async/await。

控制异步进程

使用 Generatorfunctions 也非常方便。假设有一个需求场景API需要获取,所有数据准备好后进行下一步。 

此时可以使用Generator函数让这个异步控制流程更加清晰:

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);
});

处理大数据可以节省内存

在处理大数据集时,如果一次性将所有数据加载到内存中,会造成内存浪费和程序性能下降。 

Generator函数可以用来按需处理数据,将数据一一读取并转换,减少内存占用,提高程序性能。

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);
}

实现状态机

GeneratorFunctions 也可用于实现状态机。状态机是由一组状态和状态之间的转移规则组成的数学模型,可以用来描述系统的行为和状态。 

在实际开发中,状态机可以用来处理复杂的业务逻辑,比如表单验证、工作流控制等。

使用Generator函数实现状态机的过程如下:

定义状态机的各种状态,每个状态对应一个 Generatorfunction 。

状态之间的转换是使用 Generatorfunction 的 statements.yield 实现的

调用Generator函数时,使用循环依次执行各个状态,直到状态机完成。

这是 Generator 实现的示例:

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相比,语法更加复杂,需要手动控制执行过程,使用起来相对麻烦。这也是我很少看到Generatorit被使用的原因之一。

这种语法实际上并不像看上去那样简洁易懂。 

但是在做一些复杂的控制流和状态机处理的时候还是很有用的,Generator可以让我们的流程更加清晰。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论