March 23, 2018 · 6 min · 1194 words · Bilal Durrani
I come from the .NET world, where I’m used to using LINQ for massaging
collections of data in a clear functional way.
ES6 added lots of useful functions to Array
that lets me write code like
1
[1,2,3,4].filter(x => x===3).map(x => x*2);
I like this style because it avoids deep nested loops and makes the
intention of the code very clear.
To my surprise, I learnt that I can’t write code like this for other
data structures like Map.
because these expose their data as iterators
and not as Arrays.
Iterators are a whole other beast introduced in ES6, and the only built-in way
to use filter/map with them is to convert then to Arrays.
There are serveral libraries like wu that
adds support for these operations and lots more.
Instead of just adding another library, I figured I would see how wu
worked and see if I can create something for just the parts I need.
Luckily wu is a pretty well organized library and while my
javascript foo is not that strong,
I was able to break down the basic functionality.
Lets start by trying to create an object that acts as a proxy for iterables.
We can add methods on that object like filter and map later on.
I’ll call it Chain just because I want to use this to chain a bunch of methods on iterators.
reduce(fn, initial) {
letval=initial;
letfirst=true;
for (letxofthis) {
if (val===undefined) {
val=x;
first=false;
continue;
}
val=fn(val, x);
}
returnval;
}
This is a simpler one, since we’re not going to return an iterator from this function.
If we are not provided with an initial value, we will use the first item in the iterator as the initial value.
classChain {
constructor(iterable) {
this.iterator=Chain.getIterator(iterable);
}
next() {
returnthis.iterator.next.call(this.iterator);
}
[Symbol.iterator]() {
returnthis.iterator;
}
/**
* internal filter
* @param fn
* @returns {IterableIterator<*>}
* @private
*/*_filter(fn) {
for (letxofthis) {
if (fn(x)) {
yieldx;
}
}
}
/**
* wrapper filter
* @param fn
* @returns {*}
*/filter(fn= Boolean) {
returnchain(this._filter(fn));
}
*_map(fn) {
for (letxofthis) {
yieldfn(x);
}
};
map(fn) {
returnchain(this._map(fn));
}
reduce(fn, initial) {
letval=initial;
letfirst=true;
for (letxofthis) {
if (val===undefined) {
val=x;
first=false;
continue;
}
val=fn(val, x);
}
returnval;
}
/**
* Return whether a thing is iterable.
*/staticisIterable(thing) {
returnthing&&typeofthing[Symbol.iterator] ==='function';
};
/**
* Get the iterator for the thing or throw an error.
* @param thing
* @return {*}
*/staticgetIterator(thing) {
if (Chain.isIterable(thing)) {
returnthing[Symbol.iterator]();
}
thrownewTypeError('Not iterable: '+thing);
};
}
We can add lots more functionality to this as needed, but this is a good starting point.
Here is also a gist for the whole thing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters