Javascript

Understanding Generator Function in Javascript

September 24th, 2024 | By Ezekiel Lawson | 12 min read

The generator function has been a special function in Javascript since 2015, but developers have overlooked or rarely used it,  perhaps due to its infrequent utilization or lack of familiarity. These special functions, capable of pausing and restarting execution, make handling async iteration and sequence easier. From lazy loading to custom iterables, animation to the beloved `await` keyword in asynchronous programming, generators are advanced concepts made simple.


This article will introduce beginners to generator functions, explain their importance, and explore how they streamline complex tasks like lazy evaluation and creating custom iterables. 


To fully grasp generator functions, let's take a quick refresher on regular functions in JavaScript.  This foundation will help us understand each step in this article and identify the key differences between the two.



What is a Javascript Function?


In programming, a function is a reusable block of code that performs a specific task. You can call it multiple times with different inputs (if needed) to achieve the desired functionality, and it can optionally return a result.


Declaring a Function:


    function functionName() {
    }

Let’s define a simple function: 

    function calcRectArea(length, width) {
        return length * width;
    }
    let result = calcRectArea(50, 80)


The above function `calcRectArea` takes two parameters: length and width, which represent the length and width of a rectangle, respectively. It calculates the area of the rectangle by multiplying the length and width together and returns the result.


A standard function starts, runs, and returns when the function is finished, while a generator function can be paused and resumed multiple times. Great! We just mentioned generator functions. Now, let's learn what they are and how they differ from regular functions in JavaScript.


What is a Generator Function in JavaScript?


Generator functions in JavaScript are powerful tools for generating sequences of values. They don't return a single value like a regular function when called. Instead, they return a Generator object following the ES6 Iterable Protocol. This offers a simple way to handle large datasets or create custom iterators. It uses the yield keyword to pause execution and return values one at a time. This allows them to remember their state and resume execution from the exact pause point when called again.


In JavaScript, generator functions are defined using an asterisk (*) placed after the function keyword. The code syntax of a generator function declaration.

    function* functionName(){
    }


We can also declare a generator function in an expression like a regular function, as seen in the code below:

    const generatorFunction = function*() {}


The generator function does not return a value immediately; instead, it returns a certain type of object, known as the Generator object


The generator object manages function execution through its `next()` method. The `yield` keyword within the generator function pauses execution and remembers its state. This allows the function to resume later, continuing from the pause point until completion.


Yield keyword :

    function* functionName(){
     yeild 'Name'
     yeild 'Age'
     yeild 'Employment status'
    }


Next () method:

    generator.next()


When we invoke the next() function, it yields an object comprising two properties:

* Value: This signifies the actual value of the object at the current iterator position.

* Done: This Boolean state indicates whether the iteration is complete or not.


    {value: name, done: false|true}



Calling a Generator Function


To understand the concept further, let's examine a basic code example with accurate data demonstrating how the generator function works. We will update the previous code with the accurate info we need. 

    function* profileInfo() {
      yield 'Jane Smith';
      yield 29;
      yield 'Employed';
    }
    const generator = profileInfo()
    generator.next()
    generator.next()
    generator.next()


The `profileInfo` function acts like a step-by-step information provider in our code example. It uses the `yield` keyword like a pause button, holding onto the information. When we call `profileInfo()`, it doesn't reveal everything at once. Instead, it creates a special object that remembers its place and is ready to continue. We use the `next()` method to retrieve the information piece by piece. Each time we call it, the function resumes from where it left off and delivers the next yielded value.


If we log the generator object on our console, here's what we will see:



    { value: 'Jane Smith', done: false }
    { value: 29, done: false }
    { value: 'Employed', done: false }


Calling the generator the fourth time will result in the value undefined and a true boolean value, indicating that there are no more elements to yield:



    { value: 'Jane Smith', done: false }
    { value: 29, done: false }
    { value: 'Employed', done: false }
    { value: undefined, done: true }


So, how does this all work together? Imagine you are interviewing someone and want to collect their information piece by piece. The first `next()` call would be like asking for their name, and the generator would yield 'Jane Smith'. The second `next()` call would be like asking for their age, and it would yield 34. Finally, the third `next()` call might be for their employment status, and it would yield 'Employed'.


Passing Arguments to Generator Functions for Dynamic Control


    function* addNewEmpoyee(name, age, gender, department) {
      yield `Creating new employee profile name: ${name}`;
      yield `Setting age to: ${age}`;
      yield `Setting gender to: ${gender}`;
      yield `Selecting department to: ${department}`;
      return `Employee profile: ${name}, age: ${age}, gender: ${gender}, department:${department} created.`;
    }
    const generator = addNewEmpoyee("John Doe", 30, "Male", "Technical"); // Pass arguments during creation
    
    console.log(generator.next().value); 
    console.log(generator.next().value); 
    console.log(generator.next().value);  
    console.log(generator.next().value); 
    console.log(generator.next().value);  

We define a generator function called `addNewEmployee`, which accepts four arguments:  `name`,  `age` ,  `gender`,  `department` . Inside the function, we utilize four yield statements to yield each argument. Subsequently, we use the `return`  keyword to return the four arguments. During instantiation, we pass the arguments, such as John Doe for the employee's name, and so forth, until all four arguments are provided. Finally, we invoke the generator function in our console using `generator.next()` The output below is what we will get.


    Creating new employee profile name: John Doe

    Setting age to: 30

    Setting gender to: Male

    Selecting department to: Technical

    Employee profile: John Doe, age: 30, gender: Male, department: Technical created.


In the previous code, we used the `return` keyword to return the final statement of the arguments. Let's provide a brief explanation of what the return keyword does.


Return keyword

The return statement is one of the methods used for the generator object. When a return statement is encountered within a generator function, it exits the generator's execution and returns a specific value. This effectively ends the generator's ability to yield further values.


    function* greetUser(name) {
        yield "Hello!";
        return name;
        yield "How are you";
    }
    const generator = greetUser("John Doe");
    
    console.log(generator.next());
    console.log(generator.next());
    console.log(generator.next());


Code output

  { value: 'Hello!', done: false }
 
  { value: 'John Doe', done: true }


We noticed that we have two yield statements and a return statement. When we invoke the generator function three times, we will receive a boolean statement that is true, indicating that the yield argument is complete. However, we won't receive the last yield statement, which is "How are you?" Instead, we will get a response of `{ value: undefined, done: true }`


    { value: 'Hello!', done: false }
    { value: 'John Doe', done: true }
    { value: undefined, done: true }


This is because the returned value becomes the result of the next call on the generator object. Subsequent calls to .next() after the initial return will simply return a `{ value: undefined, done: true }` object, indicating the generator is finished, as seen in the code above. Therefore, when handling the return statement, it is crucial to exercise caution, as its purpose is to terminate the function and return the current value.


Lazy loading data with Generator Function


Generator functions in JavaScript provide an effective way to implement lazy loading by yielding values on demand. This pattern can improve performance, especially when dealing with large data sets or asynchronous data sources (API). 


    function* lazyLoadFunc(data) {
        for (let item of data) {
            yield item * 3;
        }
    }
    // data array
    const numbers = [1, 2, 3, 4, 5];
    
    const lazyData = lazyLoadFunc(numbers);
    
    // process the generator lazily
    for (let printValues of lazyData) {
        console.log(printValues); 
    }

Output 

    3
    6
    9
    12
    15



Creating a custom iterable object with Generator

The Generator function can be used to define an iterable object.

    function* TrackeeEmployeeIterator(employees) {
        for (let employee of employees) {
            yield employee;
        }
    }
    const TrackeeEmployeeDatabase = {
        employees: [
            { id: 1, name: 'Gabriel', role: 'Backend Developer' },
            { id: 2, name: 'Emmanuel', role: 'Graphic Designer' },
            { id: 3, name: 'Jane', role: 'Community Manager' },
            { id: 4, name: 'Lora', role: 'Front-end Developer' },
        ],
        [Symbol.iterator]: function() {
            return TrackeeEmployeeIterator(this.employees);
        }
    };
    
    for (let employee of TrackeeEmployeeDatabase ) {
        console.log(employee);
    }


We start by creating a function `TrackeeEmployeeIterator` that takes an argument for employees. This function is a generator, indicated by the function* syntax. We use a for loop inside this function to iterate over each employee in the employees' array. The yield keyword returns each employee individually, allowing the generator to produce a sequence of employee objects.


Next, we define an array of employee objects, each with properties: `id, name, and role`. These objects represent individual employees.


To make the object iterable, we add a method `[Symbol.iterator]` to it. This special method returns an iterator created by the `TrackeeEmployeeIterator` generator function, passing in the employees array.


Finally, we use a `for...of` loop to iterate over the `TrackeeEmployeeDatabase` object. This loop utilizes the iterator to access and print each employee object to the console. The output is as shown below:

    { id: 1, name: 'Gabriel', role: 'Backend Developer' }
    { id: 2, name: 'Emmanuel', role: 'Graphic Designer' }
    { id: 3, name: 'Jane', role: 'Community Manager' }
    { id: 4, name: 'Lora', role: 'Front-end Developer' }


There are other useful examples to explore using generator functions, as demonstrated in the MDN Web Docs.

Conclusion 


The generator function offers many advantages to help you write a better function. Let’s illustrate this: Think of a generator function like GraphQL. If you have a compiled list of data and need to display only a few items, GraphQL will help you filter the queried data you need instead of displaying the entire compiled list.

Similarly, the generator function helps you collect data piece by piece. If you have a long list of data and only need the first three items, you can pick them out. If you want to continue with the fourth one, you can resume, as the generator function pauses the data and continues from where you left off.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Javascript

12 Extremely Useful Hacks for JavaScript

In this post we will share 12 extremely useful hacks for JavaScript. These hacks reduce the code and will help you to run optimized code.

April 28, 2016 | By Caio Ribeiro Pereira | 6 min read

Javascript

10 Classic Games Recreated in JavaScript

Childhood memories associated with video games can be revived with the help of JavaScript. Fall into nostalgia and find out more!

May 17, 2022 | By Jscrambler | 4 min read

Section Divider