Elegant and efficient JavaScript – Generator function

Blogger: The kitten is here
The core of the article: Elegant and efficient JavaScript – Generator function

Article directory

  • What is Generator function
  • Basic syntax of Generator function
  • The operating mechanism of the Generator function
  • Application scenarios of Generator function
    • Lazy evaluation
    • Asynchronous operations
    • Iterator
  • The difference between Generator function and ordinary function
  • Sample code

What is the Generator function

The Generator function is a new function type in ES6, which can be used to generate iterator objects. Generator functions can pause and resume functions by using the yield keyword to pause and resume code execution inside the function. The Generator function is a special type of function that can return a value multiple times during function execution and can control the execution of the function through the next method.

Basic syntax of Generator function

The definition of the Generator function is similar to that of an ordinary function, except that an asterisk (*) is added in front of the function name, as shown below:

function* myGenerator() {<!-- -->
  // function body
}

Inside the Generator function, you can use the yield keyword to pause the execution of the function and return a value. The yield keyword can appear anywhere in the function. Each time a yield statement is executed, the function will pause execution and return the value of the expression following yield to the caller as a return value. When the next method is called again, the function will continue execution from the last paused position until the next yield statement is encountered or the function ends.

Operation mechanism of Generator function

The operating mechanism of the Generator function is different from that of ordinary functions. When the Generator function is called, the function body is not executed immediately, but an iterator object is returned. By calling the next method of the iterator object, you can control the execution of the Generator function.

When the next method is called, the Generator function will execute the first yield statement and return the value of the expression after yield to the caller as the return value. At the same time, the Generator function will pause execution and wait for the next call to the next method.

When the next method is called again, the Generator function will continue execution from the last paused position until it encounters the next yield statement or the end of the function. If the Generator function is executed to the end and no new yield statement is encountered, then the done attribute of the iterator object will become true, indicating that the function execution has ended.

Application scenarios of Generator function

Lazy calculation

Lazy calculation means calculating the result only when needed. The Generator function can achieve the effect of lazy calculation through the use of the yield statement. On each call to the next method, the results can be calculated and returned as needed, rather than calculating them all at once.

For example, we can use the Generator function to implement a generator for the Fibonacci sequence:

function* fibonacci() {<!-- -->
  let a = 0;
  let b = 1;
  while (true) {<!-- -->
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3

In the above code, the fibonacci function is a Generator function that returns each item of the Fibonacci sequence through the yield statement. Each time the next method is called, the value of the next item is calculated and returned.

Asynchronous operations

The Generator function can be used to handle asynchronous operations. Through the use of the yield statement, the code for asynchronous operations can be made to look like synchronous operations.

For example, we can use the Generator function to implement an asynchronous task executor:

function* asyncTask() {<!-- -->
  const result1 = yield asyncOperation1();
  const result2 = yield asyncOperation2(result1);
  return result2;
}

function asyncOperation1() {<!-- -->
  return new Promise((resolve) => {<!-- -->
    setTimeout(() => {<!-- -->
      resolve('result1');
    }, 1000);
  });
}

function asyncOperation2(value) {<!-- -->
  return new Promise((resolve) => {<!-- -->
    setTimeout(() => {<!-- -->
      resolve(`result2: ${<!-- -->value}`);
    }, 1000);
  });
}

const task = asyncTask();
task.next().value.then((result1) => {<!-- -->
  task.next(result1).value.then((result2) => {<!-- -->
    console.log(result2); // result2: result1
  });
});

In the above code, the asyncTask function is a Generator function that pauses the execution of the function through the yield statement and waits for the result of the asynchronous operation. Each time the next method is called, the next asynchronous operation is executed and the results of the previous asynchronous operation are passed to the next asynchronous operation.

Iterator

The Generator function can be used to implement iterators. Through the use of the yield statement, an iterable object can be easily generated.

For example, we can use the Generator function to implement a simple iterator:

function* myIterator() {<!-- -->
  yield 1;
  yield 2;
  yield 3;
}

const iterator = myIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

In the above code, the myIterator function is a Generator function that returns each item of the iterator through the yield statement. Each time the next method is called, the next item of the iterator is returned.

The difference between Generator function and ordinary function

There are some syntax differences between generator functions and ordinary functions:

  • The definition of a Generator function has an asterisk (*) in front of the function name.
  • The yield keyword can be used inside the Generator function to pause the execution of the function and return a value.
  • The Generator function returns an iterator object, not a specific value.

Notes on Generator functions
When using the Generator function, you need to pay attention to the following points:

  • Generator functions cannot be defined using the arrow function syntax and can only be defined using the function keyword.
  • Arrow functions cannot be used inside the Generator function because the arrow function does not have its own this and arguments, and the yield statement inside the Generator function needs to rely on this and arguments.
  • The Generator function cannot be called using the new keyword because the Generator function returns an iterator object rather than a constructor.

Example code

The following is a complete sample code that demonstrates the basic usage and application scenarios of the Generator function:

//Lazy calculation
function* fibonacci() {<!-- -->
  let a = 0;
  let b = 1;
  while (true) {<!-- -->
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3

// Asynchronous operation
function* asyncTask() {<!-- -->
  const result1 = yield asyncOperation1();
  const result2 = yield asyncOperation2(result1);
  return result2;
}

function asyncOperation1() {<!-- -->
  return new Promise((resolve) => {<!-- -->
    setTimeout(() => {<!-- -->
      resolve('result1');
    }, 1000);
  });
}

function asyncOperation2(value) {<!-- -->
  return new Promise((resolve) => {<!-- -->
    setTimeout(() => {<!-- -->
      resolve(`result2: ${<!-- -->value}`);
    }, 1000);
  });
}

const task = asyncTask();
task.next().value.then((result1) => {<!-- -->
  task.next(result1).value.then((result2) => {<!-- -->
    console.log(result2); // result2: result1
  });
});

// iterator
function* myIterator() {<!-- -->
  yield 1;
  yield 2;
  yield 3;
}

const iterator = myIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

By using the Generator function, we can implement functions such as lazy calculation, asynchronous operations, and iterators, making the code more concise and easier to understand. At the same time, we also need to pay attention to the syntax and usage precautions of the Generator function to give full play to its functions and advantages.