To use HighlandJS in your project using Bower:
$ bower install --save highland
Example:
var $ = require('jquery'); var _s = require('highland'); _s($.when($.ready)) .flatMap(function ($) { return _s('click', $('.select-product')); }) .debounce(1000) .map(function (e) { return $(e.target).data('product'); }) .flatMap(function (product) { return _s($.getJSON('/products/' + product)) }); .errors(function (err) { console.error('Error: %s', err); }) .each(function (response) { $('.product-selected-list').append(response.html); });
This is equivalent to:
var $ = require('jquery'); var _ = require('lodash'); $(function () { $('.select-product').on('click', _.debounce(function (e) { var product = $(e.target).data('product'); $.getJSON('/products/' + product) .fail(function (err) { console.log('Error: %s', err); }) .done(function (response) { $('.product-selected-list').append(response.html); }); }, 1000)); });
Although the code in the non stream based version is shorter it is harder to reason about because of the way it is nested. Initially, stream operations like flatMap may seem alien and difficult to decipher in the stream based version.
Basically we are saying
- After the document is ready
- On click for the element
.select-product
- Wait a second (1000 ms) to check that there are no further additional clicks
- Retrieve the product identifier from the last clicked element’s data attributes
- Use this to request the product data from the server
- If there are any errors log them to the console
- Append the html in the included response to the
.product-selected-list
element.
The flatMap operation, enables you to map values to streams. The return value for the passed function should be a stream, flatMap will then merge all of the results of those returned streams as a single stream.
The first flatMap (above) is a bit more theoretical than the second as we know there should only be one document ready event as a value of that stream, therefore, we would only expect the flatMap operation to be called once. You could see it as we are replacing the original document ready stream with the click stream.
The second flatMap operation, receives the stream of product identifiers and we are returning a stream that will return a response, effectively resulting in us having a stream of streams.
For example if we were to use the basic map operation we would have:
Stream[ProductIdentifier] -> map(ProductIdentifier => Stream[Response]) -> Stream[Stream[Response]]
The flatMap operation flattens this stream of streams, effectively merging all of the streams into one single stream:
Stream[ProductIdentifier] -> flatMap(ProductIdentifier => Stream[Response]) -> Stream[Response]
In fact flatMap is equivalent to:
Stream[ProductIdentifier] -> map(ProductIdentifier => Stream[Response]) -> Stream[Stream[Response]] -> sequence() -> Stream[Response]
Where sequence is an operation that flattens a stream of streams.
In a follow-up I’ll go into details as to how using stream can result in more consise and usable patterns and how back pressure can be used to manage demand responsively and without requiring large amounts of managment code and intervention.