SoFunction
Updated on 2025-03-10

JavaScript uses generator functions to achieve elegant processing of asynchronous task flow

Generators are a special function in JavaScript that can pause execution and generate multiple values ​​as needed. With the yield keyword, generator functions can produce a new value each time they are called, making them ideal for scenarios where large amounts of data or delayed calculations are required. This article will introduce in detail the role, usage and usage of generators with other language features.

1. Definition and use of generator functions

Generator functions are defined by a special function keyword function*. Inside the generator function, you can use the yield keyword to specify the value to be generated. Here is an example of a generator function:

function* myGenerator() {
    yield 'Apple';
    yield 'Banana';
    yield 'Cherry';
}
const generator = myGenerator();
(()); // Output: { value: 'Apple', done: false }(()); // Output: { value: 'Banana ', done: false }(()); // Output: { value: 'Cherry', done: false }(()); // Output: { value: undefined, done: true }

By calling the generator function, we can obtain a generator object generator. Each call () returns an object containing the value and done attributes.

  • value represents the next generated value.
  • done means whether there are more values ​​to be generated. When all values ​​are generated, done is true.

2. Pause and resume execution

The power of generator functions is that they can pause and resume execution, which means that large amounts of data can be processed step by step when needed, rather than all at once. The following example shows how to use generator functions to process large datasets:

function* generateNumbers() {
    for (let i = 0; i <= 1000000; i++) {
        yield i;
    }
}
const numbersGenerator = generateNumbers();
for (let number of numbersGenerator) {
    (number);
}

In the above example, we define a generator function generateNumbers(), which generates a sequence of numbers from 0 to 1000000. With the yield keyword, each loop produces a new number and outputs to the console during the iteration. In this way, we can gradually deal with huge data sets and avoid performance problems caused by loading the entire data set at once.

3. Use with other language features

Generator functions can play a greater role when used in conjunction with other JavaScript features.

Iterator Protocol Iterator Protocol

Since the generator function returns an iterable object, the generated values ​​can be accessed one by one through the for...of loop.

function* shoppingList() {
    yield 'Milk';
    yield 'Eggs';
    yield 'Bread';
}
const myCart = shoppingList();
for (let item of myCart) {
    (item);
}

Deconstruction and assignment

A specific part of the generated value can be obtained by using deconstructed assignments in the generator function:

function* personDetails() {
    yield ["John", "Doe"];
    yield ["Jane", "Smith"];
}
const fullNameGenerator = personDetails();
for (let [firstName, lastName] of fullNameGenerator) {
    (firstName, lastName);
}

Combination of generator and promise

Generator functions are used in combination with asynchronous programming to achieve more flexible control flows and simplify the processing of asynchronous operations. Below we introduce how to use Promise and async/await to handle asynchronous programming in generator functions.

Using Promise:

function fetchTodos() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(['Todo 1', 'Todo 2', 'Todo 3']);
    }, 2000);
  });
}
function* todoGenerator() {
  yield fetchTodos();
}
let generator = todoGenerator();
let promise = ().value;
(todos => {
  (todos);  // ['Todo 1', 'Todo 2', 'Todo 3']
});

In the above code, we define an asynchronous function fetchTodos() which returns a Promise object that resolves an array containing to-do items after 2 seconds. We then define a generator function todoGenerator() where the fetchTodos() function is paused as the generator's value through the yield keyword.

After calling the next() method on the generator object, we can get the Promise object returned by fetchTodos(), and then we can use the .then() method to process the result of the Promise.

Using async/await:

function fetchTodo() {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      resolve('Todo');
    }, 2000);
  });
}
function* todoGenerator() {
  try {
    let result = yield fetchTodo();
    (result); // 'Todo'
    ('Generator continues...');
    // Multiple asynchronous operations can be used as needed in generator functions    let anotherResult = yield someAsyncOperation();
    (anotherResult);
    // ...
  } catch (error) {
    (error);
  }
}
async function main() {
  const generator = todoGenerator();
  try {
    while (true) {
      const { value, done } = ();
      if (done) {
        break;
      }
      await value;
    }
  } catch (error) {
    (error);
  }
}
main();

In the example above:

The () function returns a Promise object, indicating that you get a to-do item. The generator function todoGenerator() uses yield to pause execution and wait for the Promise result.

2. In the main() function, we create an iterator object generator, and perform asynchronous operations in the generator function in turn by looping and using the await keyword.

3. Multiple asynchronous operations can be used in the generator function as needed, using yield to pause execution and wait for each operation to complete. To catch possible errors, you can use the try-catch block.

PS. The generator function itself does not return a Promise object, so we need to use the generator function in conjunction with the main() function to ensure that asynchronous operations are performed in the expected order.

In general, by combining asynchronous programming features such as Promise and async/await in the generator function, the control flow of the generator function can be made more flexible, concise and readable, thereby improving the development experience of asynchronous programming.

Delegate to another Generator function

Delegating to another Generator function is a common usage of Generator function. It allows one generator function to call another generator function and yield the latter generator values ​​one by one. This delegate mechanism can simplify the code structure, improve readability, and flexibly handle collaborative relationships between multiple generators.

Sample code:

function* generator1() {
  yield 1;
  yield 2;
}
function* generator2() {
  yield 'a';
  yield 'b';
}
function* combinedGenerator() {
  yield* generator1();  // Delegate generator1()  yield* generator2();  // Delegate generator2()  yield 'Final value';
}
let generator = combinedGenerator();
(());  // { value: 1, done: false }
(());  // { value: 2, done: false }
(());  // { value: 'a', done: false }
(());  // { value: 'b', done: false }
(());  // { value: 'Final value', done: false }
(());  // { value: undefined, done: true }

In the above code, we define three generator functions: generator1(), generator2(), and combinedGenerator(). Among them, combinedGenerator() is the delegate generator function we are going to create.

In combinedGenerator(), by using the yield* expression, we can delegate the execution rights to other generator functions, that is, yield the generator values ​​of generator1() and generator2() one by one. This way, when calling the next() method on the generator object generated using combinedGenerator(), it checks whether the current generator function has a delegated generator function to call.

It is worth noting that by delegating to other generator functions, not only can the code be kept modular and reusable when merging generator values, but it can also handle more complex generator collaboration scenarios. In actual development, you can also nest multiple delegation relationships according to specific needs to achieve more flexible and efficient generator programming.

In addition, if an exception occurs in the delegated generator function (such as an error occurs in the delegated generator function and is terminated early by the active generator function), the exception will be passed back to the main generator function and thrown.

Through the delegate mechanism, the Generator function in JavaScript can better organize and control the collaboration between generators, making the code more readable, maintainable, and supports the construction of complex generator processes.

This is the article about JavaScript using generator functions to achieve elegant processing of asynchronous task flows. For more related JavaScript generator functions, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!