iterator-helper
Provide helpers that polyfill all methods defined in iterator helpers proposal, both for Iterator
and AsyncIterator
, and even more.
Installation
Install it with npm/yarn/what you want.
npm i iterator-helper
Getting started
You can wrap an iterable or an iterator with the exported iter
(for standard iterables) and aiter
(for async iterables) functions.
import { iter, aiter } from 'iterator-helper';
const iterator = iter([1, 2, 3]);
const mapped_cycle = iterator
.cycle()
.map(e => e * 2)
.asIndexedPairs();
for (const [index, element] of mapped_cycle) {
console.log(index, element);
}
You can also extend two exported classes, HIterator
and HAsyncIterator
(those names are used to avoid conflicts with possible futures Iterator
and AsyncIterator
global objects), in order to make your classes iterables.
import { HIterator } from 'iterator-helper';
class RangeIterator extends HIterator {
constructor(start, stop = undefined, step = 1) {
super();
this.position = stop === undefined ? 0 : start;
this.stop = stop === undefined ? start : stop;
this.step = step;
if (stop < start) {
throw new Error('Stop cannot be inferior to start.');
}
if (step <= 0) {
throw new Error('Step must be superior to 0.');
}
}
next() {
if (this.position < this.stop) {
const current = this.position;
this.position += this.step;
return { value: current, done: false };
}
return { value: undefined, done: true };
}
}
const range = new RangeIterator(10).filter(e => e % 2 === 0);
range.next();
range.next();
range.next();
API
There are a few methods for each sync or async iterators.
Here's the quick way, as TypeScript types with a description for each method:
interface HIterator<T, TReturn = any, TNext = undefined> {
map<R>(callback: (value: T) => R) : HIterator<R, TReturn, TNext>;
filter(callback: (value: T) => boolean) : HIterator<T, TReturn, TNext>;
take(limit: number) : HIterator<T, TReturn, TNext>;
drop(limit: number) : HIterator<T, TReturn, TNext>;
asIndexedPairs() : HIterator<[number, T], TReturn, TNext>;
flatMap<R>(mapper: (value: T) => Iterator<R> | R) : HIterator<R, TReturn, TNext>;
find(callback: (value: T) => boolean) : T | undefined;
every(callback: (value: T) => boolean) : boolean;
some(callback: (value: T) => boolean) : boolean;
toArray(maxCount?: number) : T[];
reduce<V>(reducer: (acc: V, value: T) => V, initialValue?: V) : V;
forEach(callback: (value: T) => any) : void;
count() : number;
join(glue: string) : string;
chain<I>(...iterables: IteratorOrIterable<I>[]) : HIterator<T | I>;
zip<O>(...others: IteratorOrIterable<O>[]) : HIterator<(T | O)[]>;
takeWhile(callback: (value: T) => boolean) : HIterator<T>;
dropWhile(callback: (value: T) => boolean) : HIterator<T>;
fuse() : HIterator<T>;
partition(callback: (value: T) => boolean) : [T[], T[]];
findIndex(callback: (value: T) => boolean) : number;
max() : number;
min() : number;
cycle() : HIterator<T>;
groupBy<K extends string | number | symbol>(callback: (value: T) => K) : { [Key in K]: T[] };
toIndexedItems<K>(keyGetter: (value: T) => K) : Map<K, T>;
intersection<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>;
difference<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>;
symmetricDifference<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>;
toAsyncIterator(): HAsyncIterator<T>;
}
interface HAsyncIterator<T, TReturn = any, TNext = undefined> {
map<R>(callback: (value: T) => R | PromiseLike<R>) : HAsyncIterator<R, TReturn, TNext>;
filter(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T, TReturn, TNext>;
take(limit: number) : HAsyncIterator<T, TReturn, TNext>;
drop(limit: number) : HAsyncIterator<T, TReturn, TNext>;
asIndexedPairs() : HAsyncIterator<[number, T], TReturn, TNext>;
flatMap<R>(mapper: (value: T) => AsyncIterator<R> | R) : HAsyncIterator<R, TReturn, TNext>;
find(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<T | undefined>;
every(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<boolean>;
some(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<boolean>;
toArray(maxCount?: number) : Promise<T[]>;
reduce<V>(reducer: (acc: V, value: T) => V | PromiseLike<V>, initialValue?: V) : Promise<V>;
forEach(callback: (value: T) => any) : Promise<void>;
count() : Promise<number>;
join(glue: string) : Promise<string>;
chain<I>(...iterables: AsyncIteratorOrIterable<I>[]) : HAsyncIterator<T | I>;
zip<O>(...others: AsyncIteratorOrIterable<O>[]) : HAsyncIterator<(T | O)[]>;
takeWhile(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T>;
dropWhile(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T>;
fuse() : HAsyncIterator<T>;
partition(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<[T[], T[]]>;
findIndex(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<number>;
max() : Promise<number>;
min() : Promise<number>;
cycle() : HAsyncIterator<T>;
groupBy<K extends string | number | symbol>(callback: (value: T) => K | PromiseLike<K>) : Promise<{ [Key in K]: T[] }>;
toIndexedItems<K>(keyGetter: (value: T) => K | PromiseLike<K>) : Promise<Map<K, T>>;
intersection<O>(
otherItems: AsyncIteratorOrIterable<O>,
isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is
) : HAsyncIterator<T>;
difference<O>(
otherItems: AsyncIteratorOrIterable<O>,
isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is
) : HAsyncIterator<T>;
symmetricDifference<O>(
otherItems: AsyncIteratorOrIterable<O>,
isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is
) : HAsyncIterator<T>;
}
Helpers
iter
function expose some iterator creator helpers:
There's presented as TypeScript types.
interface IIterFunction {
range(stop: number): HIterator<number>;
range(start: number, stop: number): HIterator<number>;
range(start: number, stop: number, step: number): HIterator<number>;
repeat<I>(item: I): HIterator<I>;
repeat<I>(item: I, times: number): HIterator<I>;
}
Examples
for (const i of iter.range(10)) {
}
for (const _ of iter.repeat(null, 10)) {
}
iter.repeat({ id: 1, name: 'Sialae' })
.asIndexedPairs()
.map(([index, item]) => ({ ...item, id: index + 1 }))
.filter(item => item.id % 2 !== 0)
.take(3)
.toArray();
Descriptive API
Here's is every supported method, with a small example associated.
Sync iterators
Sync iterators uses the HIterator
class/instances.
iter
(module function)
Create the iterator wrapper HIterator
from an Iterable
(Array, Set, Map...) or an Iterator
(Generator
instance, user-land iterator, ...).
import { iter } from 'iterator-helper'
iter([1, 2, 3])
iter([1, 2, 3].entries())
.from
(static method)
Do the same as iter
function call. HIterator.from([1, 2, 3])
produces the same result as iter([1, 2, 3])
.
.map<R>(callback: (value: T) => R) : HIterator<R, TReturn, TNext>
Transform each item of iterator to another value through the result of callback(item)
.
iter([1, 2, 3])
.map(item => item * 2)
.toArray()
.filter(callback: (value: T) => boolean) : HIterator<T, TReturn, TNext>
Do not yield item of iterator if callback(item)
is falsy.
iter([1, 2, 3])
.filter(item => item % 2 !== 0)
.toArray()
.take(limit: number) : HIterator<T, TReturn, TNext>
Create a new iterator that consume limit
items, then stops.
iter([1, 2, 3])
.take(2)
.toArray()
.drop(limit: number) : HIterator<T, TReturn, TNext>
Create a new iterator that ignore limit
items from being yielded, then continue the iterator as it used to be.
iter([1, 2, 3])
.drop(2)
.toArray()
.asIndexedPairs() : HIterator<[number, T], TReturn, TNext>
Get a pair [index, value]
for each value of an iterator.
iter([1, 2, 3])
.asIndexedPairs()
.toArray()
.flatMap<R>(mapper: (value: T) => Iterator<R> | R) : HIterator<R, TReturn, TNext>
Like map, but you can return a new iterator that will be flattened.
iter([1, 2, 3])
.flatMap(item => iter.range(item))
.toArray()
.find(callback: (value: T) => boolean) : T | undefined
Find a specific item that returns true
in callback(item)
, and return it. Returns undefined
otherwise.
iter([1, 2, 3]).find(item => item % 2 === 0)
iter([1, 2, 3]).find(item => item % 2 === 4)
.findIndex(callback: (value: T) => boolean) : number
Find a specific item that returns true
in callback(item)
, and return its index. Returns -1
otherwise.
iter([1, 2, 3]).findIndex(item => item % 2 === 0)
iter([1, 2, 3]).findIndex(item => item % 2 === 4)
.every(callback: (value: T) => boolean) : boolean
Return true
if each item of iterator validate callback(item)
.
iter([1, 2, 3]).every(item => item > 0)
.some(callback: (value: T) => boolean) : boolean
Return true
if at least one item of iterator validate callback(item)
.
iter([1, 2, 3]).every(item => item > 2)
.toArray(maxCount?: number) : T[]
Consume iterator (up to maxCount
items, default to infinity) and collapse values inside an array.
iter([1, 2, 3]).toArray()
.reduce<V>(reducer: (acc: V, value: T) => V, initialValue?: V) : V
Accumulate each item inside acc
for each value value
.
iter([1, 2, 3]).reduce((acc, value) => acc + value)
.forEach(callback: (value: T) => any) : void
Iterate over each value of iterator by calling callback
for each item.
iter([1, 2, 3]).forEach(console.log.bind(console))
.count() : number
End the iterator and return the number of counted items.
iter([1, 2, 3]).count()
.join(glue: string) : string
Join all the remaining elements of the iterator in a single glue string glue
.
iter([1, 2, 3]).join(', ')
.chain<I>(...iterables: IteratorOrIterable<I>[]) : HIterator<T | I>
Iterate through current iterator, then through the given iterators in the correct order.
iter([1, 2, 3])
.chain([4, 5, 6])
.toArray()
.zip<O>(...others: IteratorOrIterable<O>[]) : HIterator<(T | O)[]>
Iterate through multiple iterators together.
iter([1, 2, 3])
.zip([4, 5, 6])
.toArray()
.takeWhile(callback: (value: T) => boolean) : HIterator<T>
Continue iterator until callback
return a falsy value.
iter([1, 2, 3])
.takeWhile(item => item / 2 > 1)
.toArray()
.dropWhile(callback: (value: T) => boolean) : HIterator<T>
Skip elements until callback
return a truthy value.
iter([1, 2, 3])
.dropWhile(item => item / 2 <= 1)
.toArray()
.fuse() : HIterator<T>
Continue iterator until null
or undefined
is encountered.
iter([1, 2, 3, undefined])
.fuse()
.toArray()
.partition(callback: (value: T) => boolean) : [T[], T[]]
Partition true
elements to first array, false
elements to second one.
iter([1, 2, 3]).partition(item => item % 2 === 0)
.max() : number
Only works if it is a number iterator. Returns the maximum of iterator.
iter([1, 2, 3]).max()
.min() : number
Only works if it is a number iterator. Returns the minimum of iterator.
iter([1, 2, 3]).min()
.cycle() : HIterator<T>
When iterator ends, go back to the first item then loop. Indefinitively.
iter([1, 2, 3])
.cycle()
.take(6)
.toArray()
.groupBy<K extends string | number | symbol>(callback: (value: T) => K) : { [Key in K]: T[] }
Group by objects by key according to returned key for each object.
iter([1, 2, 3]).groupBy(item => item % 2 === 0 ? 'even' : 'odd')
.toIndexedItems<K>(keyGetter: (value: T) => K) : Map<K, T>
Index this iterator objects in a Map
with key obtained through keyGetter
.
iter([1, 2, 3]).toIndexedItems(item => `key-${item}`)
.intersection<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>
Iterate over items present in both current collection and otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
iter([1, 2, 3])
.intersection([3, 4, 5])
.toArray()
.difference<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>
Iterate over items present only in current collection, not in otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
iter([1, 2, 3])
.difference([3, 4, 5])
.toArray()
.symmetricDifference<O>(otherItems: IteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean = Object.is) : HIterator<T>
Iterate over items present only in current collection or only in otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
iter([1, 2, 3])
.symmetricDifference([3, 4, 5])
.toArray()
.toAsyncIterator(): HAsyncIterator<T>
Transform current sync iterator to an async one (wrap each new item into a resolved Promise
).
See below for available HAsyncIterator
methods.
iter([1, 2, 3]).toAsyncIterator()
Async iterators
Async iterators uses the HAsyncIterator
class/instances.
Notice: The following async generator will be used in all async examples:
async function* numbers() {
yield 1;
yield 2;
yield 3;
}
Notice: In most of the cases, when a method takes a callback, the return value can be either a value or a Promise
. If its a Promise
, it will be awaited.
aiter
(module function)
Create the iterator wrapper HAsyncIterator
from an AsyncIterable
(objects with Symbol.asyncIterator
defined) or an AsyncIterator
(AsyncGenerator
instance, user-land async iterator, ...).
import { aiter } from 'iterator-helper'
aiter({
async *[Symbol.asyncIterator]() {
yield* numbers();
}
})
aiter(numbers())
.from
(static method)
Do the same as aiter
function call. HAsyncIterator.from(numbers())
produces the same result as aiter(numbers())
.
.map<R>(callback: (value: T) => R | PromiseLike<R>) : HAsyncIterator<R, TReturn, TNext>
Transform each item of iterator to another value through the result of callback(item)
.
aiter(numbers())
.map(item => item * 2)
.toArray()
.filter(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T, TReturn, TNext>
Do not yield item of iterator if callback(item)
is falsy.
aiter(numbers())
.filter(item => item % 2 !== 0)
.toArray()
.take(limit: number) : HAsyncIterator<T, TReturn, TNext>
Create a new iterator that consume limit
items, then stops.
aiter(numbers())
.take(2)
.toArray()
.drop(limit: number) : HAsyncIterator<T, TReturn, TNext>
Create a new iterator that ignore limit
items from being yielded, then continue the iterator as it used to be.
aiter(numbers())
.drop(2)
.toArray()
.asIndexedPairs() : HAsyncIterator<[number, T], TReturn, TNext>
Get a pair [index, value]
for each value of an iterator.
aiter(numbers())
.asIndexedPairs()
.toArray()
.flatMap<R>(mapper: (value: T) => AsyncIterator<R> | R) : HAsyncIterator<R, TReturn, TNext>
Like map, but you can return a new iterator that will be flattened.
aiter(numbers())
.flatMap(item => iter.range(item).toAsyncIterator())
.toArray()
.find(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<T | undefined>
Find a specific item that returns true
in callback(item)
, and return it. Returns undefined
otherwise.
aiter(numbers()).find(item => item % 2 === 0)
aiter(numbers()).find(item => item % 2 === 4)
.findIndex(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<number>
Find a specific item that returns true
in callback(item)
, and return its index. Returns -1
otherwise.
aiter(numbers()).findIndex(item => item % 2 === 0)
aiter(numbers()).findIndex(item => item % 2 === 4)
.every(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<boolean>
Return true
if each item of iterator validate callback(item)
.
aiter(numbers()).every(item => item > 0)
.some(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<boolean>
Return true
if at least one item of iterator validate callback(item)
.
aiter(numbers()).every(item => item > 2)
.toArray(maxCount?: number) : Promise<T[]>
Consume iterator (up to maxCount
items, default to infinity) and collapse values inside an array.
aiter(numbers()).toArray()
.reduce<V>(reducer: (acc: V, value: T) => V | PromiseLike<V>, initialValue?: V) : Promise<V>
Accumulate each item inside acc
for each value value
.
aiter(numbers()).reduce((acc, value) => acc + value)
.forEach(callback: (value: T) => any) : Promise<void>
Iterate over each value of iterator by calling callback
for each item.
aiter(numbers()).forEach(console.log.bind(console))
.count() : Promise<number>
End the iterator and return the number of counted items.
aiter(numbers()).count()
.join(glue: string) : Promise<string>
Join all the remaining elements of the iterator in a single glue string glue
.
aiter(numbers()).join(', ')
.chain<I>(...iterables: AsyncIteratorOrIterable<I>[]) : HAsyncIterator<T | I>
Iterate through current iterator, then through the given iterators in the correct order.
aiter(numbers())
.chain(iter([4, 5, 6]).toAsyncIterator())
.toArray()
.zip<O>(...others: AsyncIteratorOrIterable<O>[]) : HAsyncIterator<(T | O)[]>
Iterate through multiple iterators together.
aiter(numbers())
.zip(iter([4, 5, 6]).toAsyncIterator()])
.toArray()
.takeWhile(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T>
Continue iterator until callback
return a falsy value.
aiter(numbers())
.takeWhile(item => item / 2 > 1)
.toArray()
.dropWhile(callback: (value: T) => boolean | PromiseLike<boolean>) : HAsyncIterator<T>
Skip elements until callback
return a truthy value.
aiter(numbers())
.dropWhile(item => item / 2 <= 1)
.toArray()
.fuse() : HAsyncIterator<T>
Continue iterator until null
or undefined
is encountered.
iter([1, 2, 3, undefined])
.toAsyncIterator()
.fuse()
.toArray()
.partition(callback: (value: T) => boolean | PromiseLike<boolean>) : Promise<[T[], T[]]>
Partition true
elements to first array, false
elements to second one.
aiter(numbers()).partition(item => item % 2 === 0)
.max() : Promise<number>
Only works if it is a number iterator. Returns the maximum of iterator.
aiter(numbers())).max()
.min() : Promise<number>
Only works if it is a number iterator. Returns the minimum of iterator.
aiter(numbers()).min()
.cycle() : HAsyncIterator<T>
When iterator ends, go back to the first item then loop. Indefinitively.
aiter(numbers())
.cycle()
.take(6)
.toArray()
.groupBy<K extends string | number | symbol>(callback: (value: T) => K | PromiseLike<K>) : Promise<{ [Key in K]: T[] }>
Group by objects by key according to returned key for each object.
aiter(numbers())
.groupBy(item => item % 2 === 0 ? 'even' : 'odd')
.toIndexedItems<K>(keyGetter: (value: T) => K | PromiseLike<K>) : Promise<Map<K, T>>
Index this iterator objects in a Map
with key obtained through keyGetter
.
aiter(numbers())
.toIndexedItems(item => `key-${item}`)
.intersection<O>(otherItems: AsyncIteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is) : HAsyncIterator<T>
Iterate over items present in both current collection and otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
aiter(numbers())
.intersection(iter([3, 4, 5]).toAsyncIterator())
.toArray()
.difference<O>(otherItems: AsyncIteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is) : HAsyncIterator<T>
Iterate over items present only in current collection, not in otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
aiter(numbers())
.difference(iter([3, 4, 5]).toAsyncIterator())
.toArray()
.symmetricDifference<O>(otherItems: AsyncIteratorOrIterable<O>, isSameItemCallback: (value: T, other: O) => boolean | PromiseLike<boolean> = Object.is) : HAsyncIterator<T>
Iterate over items present only in current collection or only in otherItems
iterable. O(n*m)
operation that will consume otherItems
iterator/iterable!
aiter(numbers())
.symmetricDifference(iter([3, 4, 5]).toAsyncIterator())
.toArray()
Iterator creators
iter.range
Create a range iterator (HIterator
instance) from:
- 0 to
stop
if only stop
is specified, from (-)1 by (-)1. start
to stop
, from (-)1 by (-)1 or from step
to step
for (const i of iter.range(10)) {
}
for (const i of iter.range(0, 10, 2)) {
}
for (const i of iter.range(1, 10)) {
}
for (const i of iter.range(-10)) {
}
for (const i of iter.range(20, 10)) {
}
iter.repeat
Create an finite or infinite iterator that constantly yield the same thing.
iter.repeat({ id: 1 })
iter.repeat({ id: 1 }, 5)
this is equal to this code:
iter([{ id: 1 }]).cycle()
iter([{ id: 1 }]).cycle().take(5)