Techcookies

Understanding Generator functions

JavaScript | Mon Nov 28 2022 | 5 min read

Content

  1. Generator functions

  2. Create Generator functions

  3. Consume data from Generator functions

  4. Other ways to create Generator functions

    4.1. Generator function with function expression

    4.2. Generator function with object literal

    4.3. Generator function with class syntax

  5. Passing parameter to Generator function via next()

  6. yield*

  7. Async Generator

1. Generator functions

Generators are function definition that lazly, on demand returns synchronous iteratables.

  • Lazy means here, it (generator function) doesn't execute immediately when invoked, we can fetch data explicitly whenever you need. It returns a special type of iterator, called a Generator.
  • When a value is consumed by calling the generator's next method, the Generator function executes until it encounters the yield keyword.
  • Generator function returns a new generator each time it gets called and each Generator can only be iterated once.

2. Create Generator functions

Generator functions are written using the function* syntax.

NOTE: Arrow function syntax can't be used with generator function. The function* statement defines a generator function.

js
function* getSyncData(){
    yield 1; // we can call any functions here
    yield 2;
    yield 3;
}

3. Consume data from Generator functions

js
//Option 1: Using JS destructuring
const [a, b, c] = getSyncData();
console.log(`a - ${a}, b - ${b}, c - ${c}`);

//Output
a - 1, b - 2, c - 3

//Option 2: Using spread operator
console.log(`${[...getSyncData()]}`);

//Output
1,2,3

//Option 3: Using array.from function
console.log(`${Array.from(getSyncData())}`);
console.log(`${Array.from(getSyncData(), x=> x*x)}`);

//Output
1,2,3
1,4,9

//Option 4: Using for...of loop
for(const x of getSyncData()){
    console.log(x);
}

//Output
1
2
3

//Option 5: Using next() function
const iteratable = getSyncData();
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());

//Output
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
  • yield does two thing:

    • It exits the function and return a value via next() like return.
    • It resumes the execution after yield on invocation of next().
  • When all the values has been exhausted, yield returns ```{value: undefined, done: true}``

4. Other ways to create Generator functions

4.1 Generator function with function expression

js
const getSyncData = function* (){
    yield 1;
    yield 2;
    yield 3;
}

4.2 Generator function with object literal

js
const obj = {
    *getSyncData(){
    yield 'a';
    yield 'b';
    yield 'c';
}
}

//Above object literal syntax is similar to 
var obj = {
    getSyncData: function* () {
            yield 'a';
            yield 'b';
            yield 'c';
        }
}

const [a, b, c] = obj.getSyncData();
console.log(`a -> ${a}, b -> ${b}, c -> ${c}`);

//Output
a -> a, b -> b, c -> c

4.3 Generator function with class syntax

js
class MyClass{
    * getSyncData(){
        yield 'a';
        yield 'b';
        yield 'c';
    }
}

const obj = new MyClass();
const [a, b, c] = obj.getSyncData();
console.log(`a -> ${a}, b -> ${b}, c -> ${c}`);

//Output
a -> a, b -> b, c -> c

5. Passing parameter to Generator function via next()

js
    //Option 1
    class MyClass{
        * getSyncData(list, index){
            console.log(`list ${list}`);
            console.log(`index ${index}`);
        }
    }

    const obj = new MyClass();
    const [a, b] = obj.getSyncData([1,2], 1);

    //Output
    list 1,2
    index 1

    //Option 2
    function* getSyncData(){
        const value = yield;
        console.log(`Value ${value}`);
    }
    const gen = getSyncData();
    gen.next(3); // first value will be lost
    gen.next(4); // Will pass only once
    gen.next(5); // this value will also lost
    
    //Output
    Value 4

6. yield*

yield* helps delegate to another generator function or iterator.
We can use yield* inside any generator function or with iterator and call another generator function.

js
function* getSyncData(){
    yield 1;
    yield 2;
    yield 3;
}

function* getData(){
    yield 'a';
    yield getSyncData();
    yield 'b'
}

const iteratable = getData();

console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());

//Output
{ value: 'a', done: false }
{ value: Object [Generator] {}, done: false }
{ value: 'b', done: false }
{ value: undefined, done: true }
{ value: undefined, done: true }

Now let's replace yield with yield* in getData() function.

js
function* getData(){
    yield 'a';
    yield* getSyncData();
    yield 'b'
}

const iteratable = getData();

console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());
console.log(iteratable.next());

//Output
{ value: 'a', done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 'b', done: false }

7. Async Generator

Generators can be used as async methods as well.

js
async function* getSyncData() {
    yield await Promise.resolve('a');
    yield await Promise.resolve('b');
    yield await Promise.resolve('c');
  }
  
  let str = '';
  
  async function consumeGenerator() {
    for await (const val of getSyncData()) {
      str = str + val;
    }
    console.log(str);
  }
  
  consumeGenerator();

  //Output
  abc

Note: async generators, the generator.next() method is asynchronous, it returns promises. In a regular generator we’d use result = generator.next() to get values. In an async generator, we should add await.