阿里云主机折上折
  • 微信号
Current Site:Index > Generator function syntax

Generator function syntax

Author:Chuan Chen 阅读数:18563人阅读 分类: JavaScript

Basic Concepts of Generator Functions

Generator functions introduced in ECMAScript 6 are special functions that can pause execution and resume later. These functions are defined using the function* syntax and use the yield keyword to pause execution and return a value. When called, a generator function does not immediately execute its body but instead returns a generator object.

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

const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Declaration Methods of Generator Functions

Generator functions have two main declaration methods:

  1. Function declaration form:
function* generatorFunction() {
  // Function body
}
  1. Function expression form:
const generatorFunction = function*() {
  // Function body
};

They can also be used as object methods:

const obj = {
  *generatorMethod() {
    // Method body
  }
};

How the yield Keyword Works

The yield keyword is the core feature of generator functions and has two main purposes:

  1. Pausing the execution of the generator function
  2. Returning a value to the outside of the generator

Each time the generator's next() method is called, the function resumes execution from where it was last paused until it encounters the next yield expression or the function ends.

function* countToThree() {
  console.log('Starting execution');
  yield 1;
  console.log('Continuing execution');
  yield 2;
  console.log('About to finish');
  yield 3;
  console.log('Execution complete');
}

const counter = countToThree();
counter.next(); // Outputs "Starting execution", returns {value: 1, done: false}
counter.next(); // Outputs "Continuing execution", returns {value: 2, done: false}
counter.next(); // Outputs "About to finish", returns {value: 3, done: false}
counter.next(); // Outputs "Execution complete", returns {value: undefined, done: true}

The Iteration Protocol of Generator Objects

Generator objects implement the iterator protocol, meaning they can be used in for...of loops and other contexts that accept iterables.

function* alphabetGenerator() {
  yield 'a';
  yield 'b';
  yield 'c';
}

for (const letter of alphabetGenerator()) {
  console.log(letter); // Outputs 'a', 'b', 'c' in sequence
}

The yield* Expression

The yield* expression is used to delegate to another generator or iterable object, simplifying the writing of generator functions.

function* innerGenerator() {
  yield 1;
  yield 2;
}

function* outerGenerator() {
  yield 'Start';
  yield* innerGenerator();
  yield 'End';
}

const gen = outerGenerator();
console.log(gen.next().value); // 'Start'
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 'End'

Two-Way Communication in Generator Functions

Generator functions can not only pass values outward via yield but also receive values from outside through the next() method.

function* twoWayGenerator() {
  const name = yield 'What is your name?';
  const age = yield `Hello, ${name}! How old are you?`;
  return `Info: ${name}, ${age} years old`;
}

const gen = twoWayGenerator();
console.log(gen.next().value); // "What is your name?"
console.log(gen.next('John').value); // "Hello, John! How old are you?"
console.log(gen.next(25).value); // "Info: John, 25 years old"

Error Handling in Generator Functions

Generator functions can throw errors from outside using the throw() method and catch them internally with try...catch.

function* errorHandlingGenerator() {
  try {
    yield 1;
    yield 2;
  } catch (error) {
    console.log('Caught error:', error);
    yield 3;
  }
}

const gen = errorHandlingGenerator();
console.log(gen.next().value); // 1
console.log(gen.throw('Something went wrong!').value); // Outputs "Caught error: Something went wrong!", then returns 3

Early Termination of Generator Functions

The return() method can be used to terminate a generator function early.

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

const gen = earlyTerminationGenerator();
console.log(gen.next().value); // 1
console.log(gen.return('Early end').value); // "Early end"
console.log(gen.next().value); // undefined

Practical Use Cases for Generator Functions

  1. Lazy Evaluation: Calculating values only when needed
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
  1. Asynchronous Flow Control: Simplifying the handling of asynchronous operations
function* asyncFlow() {
  const data1 = yield fetchData1();
  const data2 = yield fetchData2(data1);
  const result = yield processData(data2);
  return result;
}

// Used with a runner function
function runGenerator(gen) {
  const iterator = gen();
  
  function iterate(iteration) {
    if (iteration.done) return iteration.value;
    const promise = iteration.value;
    return promise.then(x => iterate(iterator.next(x)));
  }
  
  return iterate(iterator.next());
}
  1. State Machine Implementation: Clearly expressing finite state machines
function* trafficLight() {
  while (true) {
    yield 'Red';
    yield 'Yellow';
    yield 'Green';
    yield 'Yellow';
  }
}

const light = trafficLight();
console.log(light.next().value); // "Red"
console.log(light.next().value); // "Yellow"
console.log(light.next().value); // "Green"
console.log(light.next().value); // "Yellow"

The Relationship Between Generator Functions and Async/Await

Generator functions were introduced in ES6, while async/await was introduced in ES2017 as syntactic sugar. In fact, async/await can be seen as a more concise way to handle asynchronous operations with generator functions.

// Using generator functions for async operations
function* fetchUserData() {
  const user = yield fetch('/user');
  const posts = yield fetch(`/posts/${user.id}`);
  return { user, posts };
}

// Equivalent async/await syntax
async function fetchUserData() {
  const user = await fetch('/user');
  const posts = await fetch(`/posts/${user.id}`);
  return { user, posts };
}

Performance Considerations for Generator Functions

While generator functions provide powerful control flow capabilities, performance-sensitive scenarios require caution:

  1. Creating and calling generator functions is more expensive than regular functions
  2. Frequent pausing and resuming operations may impact performance
  3. Use with caution in hot code paths
// Performance test example
function* performanceTest() {
  let sum = 0;
  for (let i = 0; i < 1e6; i++) {
    sum += i;
    yield;
  }
  return sum;
}

const gen = performanceTest();
while (!gen.next().done) {
  // Empty loop
}

Combining Generator Functions

Multiple generator functions can be combined to create more complex control flows.

function* generatorA() {
  yield 'A1';
  yield 'A2';
}

function* generatorB() {
  yield 'B1';
  yield* generatorA();
  yield 'B2';
}

function* generatorC() {
  yield* generatorB();
  yield 'C1';
}

const gen = generatorC();
console.log(gen.next().value); // 'B1'
console.log(gen.next().value); // 'A1'
console.log(gen.next().value); // 'A2'
console.log(gen.next().value); // 'B2'
console.log(gen.next().value); // 'C1'

The Relationship Between Generator Functions and Iterators

All generator objects are iterators, but not all iterators are generator objects. Generator functions provide a convenient way to create objects that conform to the iterator protocol.

// Manually implementing an iterator
const manualIterator = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        step++;
        if (step === 1) return { value: 'Step one', done: false };
        if (step === 2) return { value: 'Step two', done: false };
        return { value: undefined, done: true };
      }
    };
  }
};

// Equivalent implementation using generator functions
function* generatorIterator() {
  yield 'Step one';
  yield 'Step two';
}

// Both can be used in for...of loops
for (const step of manualIterator) console.log(step);
for (const step of generatorIterator()) console.log(step);

Recursive Applications of Generator Functions

Generator functions can recursively call themselves or other generator functions to traverse complex data structures.

function* traverseTree(node) {
  if (!node) return;
  yield node.value;
  if (node.left) yield* traverseTree(node.left);
  if (node.right) yield* traverseTree(node.right);
}

const tree = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 }
  },
  right: {
    value: 3,
    left: { value: 6 },
    right: { value: 7 }
  }
};

for (const value of traverseTree(tree)) {
  console.log(value); // 1, 2, 4, 5, 3, 6, 7
}

Advanced Patterns for Generator Functions

  1. Coroutine Pattern: Multiple generator functions working together
function* producer() {
  while (true) {
    const value = Math.random();
    yield value;
  }
}

function* consumer() {
  let sum = 0;
  const prod = producer();
  for (let i = 0; i < 10; i++) {
    const { value } = prod.next();
    sum += value;
    yield sum / (i + 1);
  }
}

const cons = consumer();
for (let i = 0; i < 10; i++) {
  console.log(cons.next().value);
}
  1. Infinite Sequence Generation
function* naturalNumbers() {
  let n = 0;
  while (true) {
    yield n++;
  }
}

function* take(n, iterable) {
  let count = 0;
  for (const item of iterable) {
    if (count++ >= n) break;
    yield item;
  }
}

// Get the first 5 natural numbers
for (const num of take(5, naturalNumbers())) {
  console.log(num); // 0, 1, 2, 3, 4
}

Debugging Techniques for Generator Functions

When debugging generator functions, pay attention to the pause points. The following techniques can be helpful:

  1. Add logs before and after yield statements
  2. Use the debugger's step-through functionality
  3. Add custom toString() methods to generator objects
function* debugGenerator() {
  console.log('Starting execution');
  yield 'Step one';
  console.log('Continuing execution');
  yield 'Step two';
  console.log('About to finish');
  yield 'Step three';
  console.log('Execution complete');
}

const debugGen = debugGenerator();
debugGen.next(); // Outputs "Starting execution"
debugGen.next(); // Outputs "Continuing execution"
debugGen.next(); // Outputs "About to finish"
debugGen.next(); // Outputs "Execution complete"

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:for...of循环原理

下一篇:yield关键字

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.