Arrays are one of the simplest data structures which allow access to a list of elements. JavaScript (and most programming languages) has the notion of array. However one of the problems related to arrays is they are constrained by their size, i.e. an array has a limited number of elements. Furthermore, when applying functions to arrays, one passes the whole data structure at once. Let’s take a look at the following example. The map function is applied to the whole array and the result is calculated right away.
1 2 3 |
var source = [4, 6, 8, 9]; var mapped = source.map(x => x * 2); // mapped is [8, 12, 16, 18] |
Note, the lambda expression is part of ES6
In some cases, however, you may want to apply the map function to a subset of the array only. This is especially important when dealing with large arrays, where processing time may be a problem.
What are generators?
Another cool feature of ES6 is the the so called generators. A generator produces a (possibly infinite) sequence of elements. Compared to arrays, generators produce a sequence without unknown size. The only way to get to know it, is to iterate over all elements of the sequence. This may not be possible with infinite sequences, though. If you are familiar with the IEnumerable interface in C#, then you would probably get the idea. How does a generator definition look like?
1 2 3 4 5 6 7 8 9 |
function* sequence(start) { var i = 0; while (true) yield i++; } var seq = sequence(5); for (var value of seq) console.log(value); |
The first thing to notice here is the star sign after the function keyword. This is how generators are defined. Inside the generator definition one would use yield to produce intermediate results. Another interesting thing to notice here is how one iterates over the generator – the for-of loop, which is a new feature in ES6 specially for iterable data structures (f.x. arrays, Set, Map). In this case, however, we have created an infinite generator and this for-of loop will make your browser hang. The for-of loop is nothing but syntactic sugar for the following:
1 2 3 4 5 |
var seq = sequence(); var next; while (!(next = seq.next()).done) { console.log(next.value); } |
Every iterable data structure implements the Iterator pattern. In JavaScript it is an object, that provides a method next(). This method returns every time an object with the following format: { value: …, done: false }. If the generator is infinite, you would never get a next result with done = true.
Pros & cons
Generators are very useful when you deal with large data sets and you don’t want to work on the entire set at once. Generators give you the opportunity to work with one element at a time and you have the power to stop revealing other elements. Another advantage is that one can chain generators, for example map(). On the other side one may get into the trap of infinite sequences and hang the browser, if not careful.
Starter kit
I have created a simple repository on GitHub with some basic generators one may think of. It includes definitions for sequence(), identity(), map(), filter(), aggregate(). You are more than welcome to send me a pull request with other generators to include 🙂
LINQ
If you are a .NET developer, you are most probably familiar with LINQ and the chaining of IEnumerable. There are already some JavaScript libraries that offer this functionality in the browser or server (this and that). These libraries, however, were written in times where generators didn’t exist and their authors had to program the Iterator pattern in JavaScript. I decided to take a look at the most popular LINQ methods and to implement them by means of generators – that is how the jslinq.next was born. This means you could do the following:
1 2 3 4 |
var result = jslinq.from(source).select(x => x * 2).where(x => x % 3).take(5).build(); for (var item in result) { console.log(item); } |
Browser support
We can already use generators in Chrome 39+ and Firefox 26+. Apart from that, some transpilers, f.x. Traceur, and polyfills can also help you. You can check the full ECMAScript compatibility table.
Conclusion
Generators give us a certain degree of freedom when dealing with (possibly infinite) lists of items. We can transform them or filter them on the fly without the need to read the entire sequence. (PS: Operations like reverse require, though, at one reads the entire sequence).