@t0bst4r/stream v1.0.10
@t0bst4r/stream
Add java-like high performance stream api to your application
Installation and Usage
Installation via npm
npm install @t0bst4r/stream
Usage
Import the package to your project files
import * from "@t0bst4r/stream";
or use single imports
import { Stream } from "@t0bst4r/stream/dist/stream";
import "@t0bst4r/stream/dist/add/map";
import "@t0bst4r/stream/dist/add/flatMap";
import "@t0bst4r/stream/dist/add/filter";
import { findFirst } from "@t0bst4r/stream/dist/collectors/findFirst";
import { toList } from "@t0bst4r/stream/dist/collectors/toList";
import { groupBy } from "@t0bst4r/stream/dist/collectors/groupBy";
Create a new stream of any Iterable and do your stuff (like Array)
To create a new stream use Stream.Of(yourIterable)
or Array<T>.prototype.stream()
.
const simpleStream: Stream<number> = [ 1, 2, 3, 4, 5 ].stream();
// produces a stream of [ 1, 2, 3, 4, 5 ]
const myStream: Stream<string> = Stream.Of([ "hello", "world", "some", "values" ]);
// produces a stream of [ "hello", "world", "some", "values" ]
const mappedStream: Stream<number> = myStream.map(str => str.length);
// produces a stream of [ 5, 5, 4, 6 ]
const filteredStream: Stream<number> = mappedStream.filter(num => num != 4);
// produces a stream of [ 5, 5, 6 ]
const flatMappedStream: Stream<number> = filteredStream.flatMap(num => [num, num * 2, num * 3]);
// produces a stream of [ 5, 10, 15, 5, 10, 15, 6, 12, 18 ]
Collectors
Then just collect your items:
filteredStream.collect(toList());
// produces: [ 5, 5, 6 ]
mappedStream.collect(findFirst());
// produces: 5 (or undefined if the stream is empty)
myStream.collect(groupBy(item => item.length));
// Stream<T> produces a Map<TKey, T[]>:
// - key: 5, items: [ "hello", "world" ]
// - key: 4, items: [ "some" ]
// - key: 6, items: [ "values" ]
myStream.collect(groupBy(item => item.length, item => item.toUpperCase() ));
// Stream<T> produces a Map<TKey, TValue[]>:
// - key: 5, items: [ "HELLO", "WORLD" ]
// - key: 4, items: [ "SOME" ]
// - key: 6, items: [ "VALUES" ]
Collectors can be used for arrays or any other iterable, too.
You can create your own Collectors, too. Just implement the Collector<T, R> interface.
Iterable and Iterator
This Stream API uses ES6 Iterables and Iterators to process the data. A stream can be created on any iterable like arrays, other streams, or custom iterable implementations.
It also implements the Iterable interface itself so that you could iterate over any stream object:
const myStream: Stream<string> = [ "hello", "world", "some", "values" ].stream();
for (let str of myStream) {
console.log(str);
}
Every collector gets its own iterable/iterator over the mapped/filtered data so that you can reuse every stream:
let myStream: Stream<number> = [ "hello", "world", "some", "values" ].stream()
.map(str => str.length);
let firstItem: number = myStream.collect(findFirst()); // 5
let allItemsWithoutFour: number[] = myStream.filter(len => len != 4)
.collect(toList()); // [ 5, 5, 4, 6 ]
myStream.forEach(len => console.log(len)) // "5" "5" "4" "6"
Implement custom stream functions / extend the stream API
You can simply add custom methods to the stream class by defining new methods:
import { Stream } from "@t0bst4r/stream";
declare module "@t0bst4r/stream" {
interface Stream<T> {
yourCustomMethod(...): Stream<T>;
}
}
Stream.prototype.yourCustomMethod = function<T>(...): Stream<T> {
// do your stuff.
// create a new stream or just
return this;
}
Feel free to create pull requests to help me improving this API
Differences to JavaScript Array functions
The JavaScript Array functions run immediately after adding the filter or map statements to the Array.
Methods of this Stream API will only be executed if there is a collector collecting the items.
For example if you just want the first element after filtering and mapping an Array all items will be mapped and filtered:
const myItem: number = [ "hello", "world", "some", "values" ]
.map(str => str.length) // [ 5, 5, 4, 6 ]
// mapping callback will be called for each item (4 times)
.filter(len => len == 5) // [ 5, 5 ]
// filter callback will be called for each item (4 times)
[0]; // 5
With this Stream API the findFirst-Collector will only process the items until the first item matches.
const myItem: number = [ "hello", "world", "some", "values" ].stream()
.map(str => str.length)
// will be only called once (while collecting)
.filter(len => len == 5)
// will be only called once (while collecting)
.collect(findFirst()) // 5
If you do this without the JavaScript Array methods and without this Stream API this could look like this:
function findFirstMappedAndFiltered<T, R>(items: T[], mapCallback: (item: T) => R, filterCallback: (item: R) => boolean): R | undefined {
for(let item of items) {
let mappedItem: R = mapCallback(item);
if (filterCallback(mappedItem)) {
return mappedItem;
}
}
return undefined;
}