Generator Functions in TypeScript

Generator Functions in TypeScript

This article explains generator functions in TypeScript.

You can learn everything from the basics of how to use generator functions to advanced examples combined with asynchronous processing, along with code samples.

YouTube Video

Generator Functions

Generator functions in TypeScript provide similar functionality to JavaScript's generator functions. Generator functions are defined using function* (a function declaration with an asterisk) and are special functions that can pause and resume execution unlike normal functions.

When a generator function is called, an iterator is returned, which generates values one at a time through this iterator, and you can pause execution using the yield keyword or send values from outside.

Basic Syntax of Generator Functions

 1function* myGenerator(): Generator<number, void, unknown> {
 2    yield 1;
 3    yield 2;
 4    yield 3;
 5}
 6
 7const gen = myGenerator();
 8
 9console.log(gen.next().value); // 1
10console.log(gen.next().value); // 2
11console.log(gen.next().value); // 3
12console.log(gen.next().done);  // true (Iteration finished)
  • Define a generator function with function* myGenerator().
  • The yield keyword pauses the function execution while returning a value.
  • Each time the next() method is called, the execution of the generator function resumes and progresses to the next yield.

next() returns an object containing the next value and a done property. When done is true, it indicates that all values have been generated and the generator's processing is complete.

Applications of Generator Functions

Using generator functions allows for easy representation of sequential processing. In the following example, we create a generator function that generates a sequence of numbers.

 1function* sequenceGenerator(start: number = 0, step: number = 1) {
 2    let current = start;
 3    while (true) {
 4        yield current;
 5        current += step;
 6    }
 7}
 8
 9const seq = sequenceGenerator(1, 2);
10
11console.log(seq.next().value); // 1
12console.log(seq.next().value); // 3
13console.log(seq.next().value); // 5
  • In this example, sequenceGenerator generates an indefinitely increasing sequence of numbers. Use yield to return values at each step, and generate the next value on subsequent calls.

Passing Values to next

The next() method can receive a value, which can be sent into the generator function.

 1function* adder() {
 2    const num1 = yield;
 3    const num2 = yield;
 4    yield num1 + num2;
 5}
 6
 7const addGen = adder();
 8addGen.next();          // Initialization
 9addGen.next(5);         // Set 5 to num1
10const result = addGen.next(10).value; // Set 10 to num2 and get result
11console.log(result);    // 15
  • In this example, next(5) and next(10) send their respective values into the generator function, and yield num1 + num2 returns their sum.

return and throw

  • return(value) can terminate the generator and return the specified value.
  • throw(error) can throw an exception inside the generator, used for handling exceptions within the generator.
 1function* testGenerator() {
 2    try {
 3        yield 1;
 4        yield 2;
 5    } catch (e) {
 6        console.error("Error caught:", e);
 7    }
 8}
 9
10const gen = testGenerator();
11console.log(gen.next().value); // 1
12gen.throw(new Error("An error occurred!")); // Error caught: An error occurred!
  • In this example, the throw method is used to generate an error inside the generator, and that error is caught within the generator.

Type Definition in TypeScript

The type definition of a generator function can be specified in the following format.

1// Generator<YieldType, ReturnType, NextType>
2function* myGenerator(): Generator<number, void, unknown> {
3    yield 1;
4    yield 2;
5    yield 3;
6}
  • You specify types in the form of Generator<YieldType, ReturnType, NextType>.
    • YieldType is the type of the value returned by yield.
    • ReturnType is the type of the value returned by return.
    • NextType is the type of the value passed to next().

In the following example, specific types are specified to use the generator safely with types.

 1function* numberGenerator(): Generator<number, void, number> {
 2    const num1 = yield 1;
 3    const num2 = yield num1 + 2;
 4    yield num2 + 3;
 5}
 6
 7const gen = numberGenerator();
 8
 9console.log(gen.next().value);   // 1
10console.log(gen.next(10).value); // 12 (10 + 2)
11console.log(gen.next(20).value); // 23 (20 + 3)

Generators and Asynchronous Processing

Generators can also be utilized for asynchronous processing. For example, you can use yield to wait for the results of asynchronous operations while proceeding with sequential processing. However, in TypeScript or JavaScript, async/await is more commonly used.

1function* asyncTask() {
2    const result1 = yield fetch("https://codesparklab.com/json/example1.json");
3    console.log(result1);
4
5    const result2 = yield fetch("https://codesparklab.com/json/example2.json");
6    console.log(result2);
7}

Thus, while you can process asynchronous operations sequentially with generators, they are not commonly used for asynchronous processing because Promises and async/await are more convenient.

Summary

  • Generator functions are special functions defined with function* that can return values with yield while pausing execution of the function.
  • Use next() to resume the generator and receive values. Additionally, you can send values into the generator using next(value).
  • You can use return() and throw() to terminate generator functions or handle errors.
  • When using generators in TypeScript, you can utilize type definitions to write type-safe code.

Generators are powerful tools that allow you to flexibly control iteration.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video