When we have an array, we typically use the ‘for loop’ to iterate over its element.
- The ‘for loop’ uses the variable 'i' to track the index of the ranks array
- The value of 'i' increments each time the loop executes as long as the value of 'i' is less than the number of elements in the ranks array. But its complexity grows when you nest a loop inside another loop.
ES6 introduced a new loop construct called ‘for...of’ to eliminate the standard loop’s complexity
The ‘for...of the loop’ can create a loop over any iterable object, not just an array.
The following values are iterable –
Plain objects are not iterable and hence the 'for...of' uses the Symbol.iterator.
The Symbol.iterator is a special-purpose symbol made especially for accessing an object's internal iterator. So, you could use it to retrieve a function that iterates over an array object, like so –
- An iterator is an object that can access one item at a time from a collection while keeping track of its current position
- It just requires that you have a method called next() to move to the next item to be a valid iterator
- The result of next() is always an object with two properties –
- Value: The value in the iteration sequence
- Done: true | false
Generator functions once called, returns the Generator object, which holds the entire Generator iterable and can be iterated using next() method. Every next() call on the generator executes every line of code until it encounters the next yield and suspends its execution temporarily.
- This generator object needs to be assigned to a variable to keep track of the subsequent next() methods called on itself.
- If the generator is not assigned to a variable then it will always yield only till the first yield expression on every next().
- A generator function is a function marked with the * and has at least one yield-statement in it.
- Syntactically they are identified with a *, either function* X or function *X, — both mean the same thing
Fun Fact – async/await can be based on generators
Generator functions/yield and Async functions/await can both be used to write asynchronous code that 'waits', which means code that looks as if it was synchronous, even though it is asynchronous. ... An async function can be decomposed into a generator and promise implementation which is good to know stuff.
Generator functions are written using the function* syntax –
- ‘function*’ is a new 'keyword' for generator functions
- yield is an operator with which a generator can pause itself
Additionally, generators can also receive input and send output via yield. In short, a generator appears to be a function but it behaves like an iterator.
A generator is a function that returns an object on which you can call next(). Every invocation of next() will return an object of shape —
- The value property will contain the value
- The done property is either true or false
- When the done becomes true, the generator stops and won’t generate any more values
Here are some other common definitions of generators
- Generators are a special class of functions that simplify the task of writing iterators
- A generator is a function that produces a sequence of results instead of a single value, i.e you generate a series of values
- The value property will contain the value. The done property is either true or false. When the done becomes true, the generator stops and won’t generate any more values. The yield is a magical keyword that can do more things other than simply return a value and next() can do more things aside from retrieving the value.
- A passing argument to next() - The argument passed to next() will be received by yield –
Passing a function to yield
Apart from returning values, the yield can also call a function –
Delegating to another generator or iterable using yield* expression
Let’s see an example of fetching a single value from an API
As a Title –
- In this example; to fetch data from API, we have to install node-fetch using the command –
'npm install node-fetch'
- We then pass a generator to a function as a parameter. Let's call the function getTitle()
- Now, in the function, we have to go through some steps to execute the generator
- Initially, we will call the generator method. It returns an iterator(object) which is caught in a variable 'iterator'
- Now execute the iterator using 'next()' method
- When the next method is called, the generator starts executing from this point. At line 4 the 'URL' is fetched
- Fetch returns an object which is captured into variable 'iteration'
- The object has 2 fields, viz. 'value' and 'done'
- Here value is a promise and done is a boolean set to false
- Iteration has a promise. Here we used our first yield
- In our case, the getTitle() function has to resolve the promise. Since the yield doesn’t know how to resolve the promise. So for resolving that promise we caught iteration.value into a variable 'promise'
- Now we resolve the promise say into variable 'x'
- We send the resolved 'x' to the iterator's next method. This x is a response which we collect into a variable 'response'. (line 5)
- Now we have to extract the post from the response
- The response object now again has a promise which is to be resolved by our function. So we extract the object into variable 'anotherIterator' and the value of that object viz. promise into 'anotherPromise'
- Now we resolve that promise in 'y' and pass it to the generator through the next() method
- Here we used the second yield. So now we have resolved the response.json() and caught it into variable 'post'. (line 6)
- Finally, we get our title through the object 'post'.Now we are going to extract our title from 'post.title'. Check the console for the title
Advantages of Generators
This is an evaluation model that delays the evaluation of an expression until its value is needed. That is, if the value is not needed, it will not exist. It is calculated on demand.
A direct outcome of Lazy Evaluation is that generators are memory efficient.
The only values generated are those that are needed. With normal functions, all the values must be pre-generated and kept around case they need to be used later.
We have learned the following things about iterators and generators –
- Keeping iteration logic with the object it belongs to, is a good practice and which is the focus of ES6 features
- The ability of a function to exchange data with the calling code during the execution is unique. And, surely, they are great for making iterable objects
- As can be evidenced by the examples, generators are a really powerful tool that lets you have cleaner code - especially when it comes to any kind of asynchronous behaviour