Artwork: https://code-art.pictures/
Imagine a library that:
- Like Ramda, provides many useful functions for iterables manipulation
- Reads familiar, like standard Array iterative methods
- Doesn't try to replace JavaScript built-ins like
Array
,Set
, or the iteration protocol - Like Java Streams, includes Optional to clearly distinguish between “no value” and “
undefined
as a value” - Is compact in terms of bundle size
I couldn't find one that fits all these needs, so I created my small library — fluent-streams.
Talk is Cheap. Show Me the Code
Top 3 Words in a Text
const words = ['Lorem', 'ipsum', /* ... */]
stream(words)
.groupBy(word => word.toLowerCase())
.map(([w, {length}]) => [w, length])
.sortBy(([, length]) => -length)
.take(3)
.toArray()
// => ['ut', 3], ['in', 3], ['dolor', 2]
Coprime Integers
// Endless stream of 2..999 integers
const randomInts = continually(() =>
2 + Math.floor(Math.random() * 998)
)
randomInts
.zip(randomInts)
.filter(([a, b]) => gcd(a, b) === 1)
.distinctBy(pair => stream(pair).sortBy(i => i).join())
.take(10)
// => [804, 835], [589, 642], [96, 145], ...
- Streams can be endless, as shown, but you can limit them to the first
n
items using thetake(n)
method - A stream can be reused multiple times, even in parallel. This is because streams are stateless and only store a reference to the input. The state is created only when the stream spawns an iterator.
Generate a Deck of Cards
const deck = streamOf('♠', '♥', '♣', '♦')
.flatMap(suit =>
streamOf<string | number>(
'A',
...range(2, 11),
'J',
'Q',
'K'
).map(rank => `${rank}${suit}`)
)
// => 'A♠', '2♠', '3♠', ...
And Play Hold'em Poker!
const playersNum = 2
const [flop, turn, river, ...hands] = deck
.takeRandom(3 + 1 + 1 + playersNum * 2)
.splitWhen((_l, _r, i) =>
i === 2 // flop
|| i === 3 // turn
|| i >= 4 // river
&& i % 2 === 0 // ...players' hands
)
// flop = ['3♦', '9♣', 'J♦']
// turn = ['4♣']
// river = ['7♦']
// hands = ['J♠', 'Q♥'], ['10♠', '8♥']
First player has a pair of Jacks on the flop, while the second player gets a straight, but only on the river. Who will win?
This Must Be Cheap
Everything above can be achieved with only native data structures. However, code written with Fluent Streams reads nicer. While making code more readable is a perfectly valid goal, the cost of achieving it should be low in terms of cognitive load, bundle size, and performance.
And that’s exactly the case with Fluent Streams! Here’s why:
- No learning curve: The API feels familiar, resembling standard Array methods. It’s easy to add and just as easy to remove.
- No reinvention: The library doesn’t create new data structures or protocols — it builds on JavaScript’s already robust features.
- Minimal bundle impact: At just near 9 kB minified, it’s lightweight. If your project already includes React and its satellite libraries, which weigh hundreds of kilobytes, this addition is hardly noticeable.
- Lazy processing: The library processes items lazily, which can reduce memory usage and improve efficiency in long pipelines by avoiding unnecessary intermediate data copying.
Caveats
The library is shipped untranspiled to ES5. This decision is driven by the desire to maintain a small bundle size, which is achieved by leveraging ES6+ features that enable iteration with very concise code — most notably generators. However, only widely supported language features are utilized.
If you are still transpiling to ES5, you can use the library by transpiling it yourself and adding polyfills. Be aware, though, that this will increase the bundle size, so it’s not recommended. Instead, this might be the perfect occasion to revisit your build configuration and embrace modern JavaScript features.
Top comments (2)
Check this out
Great lib. Its successor, Sequency, was one of my inspirations