DEV Community

Cover image for Understanding Generators in JavaScript with examples
Hamza Elkotb
Hamza Elkotb

Posted on

Understanding Generators in JavaScript with examples

First of all: what is Generators in js?🤔

  1. Generators are functions.
  2. Generator Function Run Its Code When Required.
  3. Generators Are iterables.
  4. It doesn't run unless you need it.
  5. Generator Function Return Special Object [Generator Object].
  6. That Object can loop on generator function data.

❗NOTE: The data is present but when the function run, it generates the data.

Briefly:

Generators functions are different from normal function it doesn't run until you need it, they are iterable & return special object.

Image description

Example from real live:

like tickets generator device, when you pay it generates new serial number and print on new ticket then you get it, Here serial number already exists but it didn't print until get order to create a new ticket.

Generators Methods:

next() to get next yield.
return() to return a value & stop generating.
throw() to throw an error & stop generating.

Generators properties:

done if iteration done or not, return boolean value.
value current yield value.

Syntax:

function* generatorName(){
}
Enter fullscreen mode Exit fullscreen mode

Now you can put in it any JS code and also can add the data that want to generate and we'll see how in next paragraph👇.

Yield Data | Data Production Process

Yield keyword used to data production process, and then you can loop on it using next() with Generator Object.

so you use yield keyword and put it's value.

function* generatorName(){
  yield "hello";
  yield "bye";
  yield 1;
  yield 2;
}
Enter fullscreen mode Exit fullscreen mode

you can put any type of data (boolean, object...)

lets try Looping on Generator function Yields🔃

for (let i of generateNums()){
  console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

the output will be:
"hello"
"bye"
1
2
this also what next() method does!

Create Generator Object🏭

let generator = generatorName();
Enter fullscreen mode Exit fullscreen mode

lets see something will make this clear to you💯
lets check typeof

console.log(typeof generator); // object
console.log(typeof generatorName); // function
console.log(generator); // Object: Generator {...}
Enter fullscreen mode Exit fullscreen mode

next method | Accessing Yields

It returns value & done state

console.log(generator.next()); // Object { value: "hello", done: false }
console.log(generator.next()); // Object { value: "bye", done: false }
console.log(generator.next()); // Object { value: 1, done: false }
console.log(generator.next()); // Object { value: 2, done: false }
Enter fullscreen mode Exit fullscreen mode

As you note every time you use next() method, it returns the next yield and can't undo this action.⏩

Now we looped on all yields, if we use next() again guess what we'll happen!🤔

console.log(generator.next()); // Object { value: undefined, done: true }
Enter fullscreen mode Exit fullscreen mode

yes, it returned value: undefined that's because there is no more yield to access, also you'll find done: true because loop is over.

value property:

Use it with next() method to get the current yield value.

console.log(generator.next().value); // undefined
Enter fullscreen mode Exit fullscreen mode

I want you to think why it returned undefined.🤔
I'll be 😊 if you answered it true in comments.

done property:

console.log(generator.next().done); // true
Enter fullscreen mode Exit fullscreen mode

return & throw Methods | Controlling yield process

1- return

Use return method to stop yielding process, making done:true & value: undefined also you can return a custom value.
Let's see an example😬

these are our generator function & generator object:

function* letter(){
  yield "A";
  yield "B";
  yield "C";
}
let gen2 = letter();
Enter fullscreen mode Exit fullscreen mode

also we can access normally:

console.log(gen2.next()); // Object { value: "A, done: false }
console.log(gen2.next()); // Object { value: "B", done: false }
Enter fullscreen mode Exit fullscreen mode

Now if we used return method this is what will happen

console.log(gen2.return()); // Object { value: undefined, done: true }
Enter fullscreen mode Exit fullscreen mode

As you noticed we still have one yield we didn't access it before, plus if you used next() method again you'll get { value: undefined, done: true }.
that is what return is here to do!

also you can return any value, by putting it between ()

console.log(gen2.return("visit youtube.com/devcoder")); 
Enter fullscreen mode Exit fullscreen mode

now output will change to { value: "visit youtube.com/devcoder", done: true }.

2- throw

It's like return() method, but it throws custom errors like throw keyword.

let gen3 = letter();
console.log(gen3.next()); // Object { value: "A, done: false }
console.log(gen3.throw("Hi coder!")); // Error: Uncaught Hi coder!
console.log(gen3.next()); // not working
Enter fullscreen mode Exit fullscreen mode

❗NOTE: throw() method doesn't stop your code, just stop the generator object.

Use Case: for example we have a generator function to generate random numbers, if that number is duplicated, it throws error.

Loop on generator yields

if we tried to loop through gen2 no output will appear

for (let i of gen2){
  console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

that's because we already accessed gen2 in past examples, so will create new generator object and loop though

let gen4 = letter()
for (let i of gen4 ){
  console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

try that, will work.✅

Generate Infinite Numbers

function* infinity(){
  let index = 0
  while(true){
    yield index++
  }
}
let inf = infinity();
Enter fullscreen mode Exit fullscreen mode

Now as you see we're able to add normal JS code inside generator function,
1- when inf visit infinity() for first time index = 0 and will enter the infinite loop and will not exit
2- in loop, every time we use next(), index will increment and yield will equal the new index value.

NOW, let's use next() on it

console.log(inf.next()); // Object { value: 0, done: false }
console.log(inf.next()); // Object { value: 1, done: false }
console.log(inf.next()); // Object { value: 2, done: false }
console.log(inf.next()); // Object { value: 3, done: false }
...
Enter fullscreen mode Exit fullscreen mode

🔴IMPORTANT

yield*

What is deference between yield & yield*, the answer will be clear if we try it with an array.👇

function* GeneAll(){
  yield* [4,5];
  yield [6,7];
}

let gen754 = GeneAll()
Enter fullscreen mode Exit fullscreen mode

Now let's try to access for one time

console.log(gen754.next()); // Object { value: 4, done: false }
Enter fullscreen mode Exit fullscreen mode

as you noticed it returned only the first element from our array.
let's try again!

console.log(gen754.next()); // Object { value: 5, done: false }
Enter fullscreen mode Exit fullscreen mode

Now it returned the second element.
let's try again!

console.log(gen754.next()); // Object { value: [6,7], done: false }
Enter fullscreen mode Exit fullscreen mode

But now it returned the full array, why???🤯

the yield* treat value as individual elements, so it converts the array value into more small values, but yield doesn't do this.
The same thing applies to string!

Delegate Generator Function | (Generator Function Chaining)

  • it's a function that delegate another function yield

take an example🎴

function* nums(){
  yield 1;
  yield 2;
  yield 3;
}

function* letter(){
  yield "A";
  yield "B";
  yield "C";
}

function* AllYields(){
  yield nums(); // without starter"*" will return the name of function
  yield* nums(); // this will include an array contains all nums() yields
  yield* letter()
  yield [6,7]; // without starter"*" it'll return an array
}

let gen55 = AllYields()
Enter fullscreen mode Exit fullscreen mode

Let's see what will happen in first next()

console.log(gen55 .next()); // Object { value: Generator, done: false }
Enter fullscreen mode Exit fullscreen mode

as you saw it returned the function name

console.log(gen55.next()); // Object { value: 1, done: false }
console.log(gen55.next()); // Object { value: 2, done: false }
console.log(gen55.next()); // Object { value: 3, done: false }
Enter fullscreen mode Exit fullscreen mode

What happened behind the since?🤯
1- gen55 will go to every yield and enter it to access all data in it.
2- it will find that first yield without * and contains a generator function nums().
3- will return it's name.
4- after that gen55 will go to next yield "yield* nums()" with * so, will go through it.
5- it will find that it contains other yields so, will go through every yield and return it on every next() method call until it end accessing all nums() yields.
6- and now it went through out our second yield "yield* nums()" .
7- Then go to next yield in AllYields() and repeat the same process etc...

Conclusion

This was all what I know about generators in javascript, I hope you enjoyed with this article and learned a new thing,
I'll be happy for that.
If you have any notes about this article or new information tell me about in comments.
Follow me on YouTube , GitHub .
For Coding T-shirts visit: CodingShirts .
And don't forget to read these references👇.
See you again, Bye👋

References

MDN: Generator
MDN: function*
MDN: Iterators and generators
digitalocean: understanding generators in JS
JavaPoint: ES6 Generators

Top comments (0)