chain

Simple monad like wrapper.

See the ChainContainer for more.

Very helpful for taking values and then progressively working on them, instead of continually wrapping deeper in method calls.

Calling chain(3) - gives an object you can then chain calls against:

  • .close() - gets the value of the current chain
  • .chain(function) - where it is passed the value, and returns a new Chain with that value.
  • .errorHandler(fn) - custom function called if an error is ever thrown
  • .debug() - console.logs the current value, and continues the chain with that value

Along with methods that can iterate on each element, assuming the value in the chain is an Array.

  • .chainMap(function) - where it calls the .map on value, and applies the function on every item in the array, storing the result from the function.
    (Useful for changing values without changing the original object))
  • .chainForEach(function) - where calls .forEach on value, and applies the function on every item, without storing the result from the function.
    (Useful for changing objects in-place)
  • .chainFlatMap(function) - where it calls .flatMap on value, and applies the function on every item, flattening the results.
    (Useful for expanding an array based on values in the array)
  • .chainFilter(function) - where it calls .filter on value, using the function on every item, keeping the item in the list if the function returns true.
    (Useful for removing items from an array)
  • .chainReduce(function, initialValue) - where it calls .reduce on value, and reduces the value to a single result.
    (Useful for reducing the array to a single value
    - like a concatenated string or sum total)

There may be times you want to run side effects, or replace the value entirely. (This isn't common, but may be useful on occasion)

  • .execute(function) - where it calls a function, but doesn't pass on the result.
    (This is useful for side-effects, like writing to files)
  • .replace(value) - replaces the value in the chain with a literal value, regardless of the previous value.

For example:

addTwo = (value) => value + 2;

//-- we can always get the value
utils.chain(3).close();  // 3

but this is much easier if we continue to chain it

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(addTwo) // (3 + 2)
  .chain(addTwo) // (5 + 2)
  .debug() // consoles 7 and passes the value along
  // define a function inline
  .chain((value) => value + 3) // (7 + 3)
  .close()

// consoles out value `7`
// returns value 10

Note that we can also map against values in the array

initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
initializeArray(3); // [0, 1, 2]

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(initializeArray) // [0, 1, 2]
  .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
  .chainMap(addTwo)
  .close();
// [4, 5, 6]

Chain to log results while transforming values

results = [{ userId: 'abc123' }, { userId: 'xyz987' }];

activeUsers = chain(results)
 .chainMap((record) => users.get(record.userId))
 .chainForEach(record => record.status =  'active')
 .chain(records => d3.csv.format(records))
 .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
 .close()

Or even combine with other utility methods

badStr = 'I%20am%20the%20very%20model%20of%20a%20modern%20Major'
 + '-General%0AI\'ve%20information%20vegetable%2C%20animal%2C%20'
 + 'and%20mineral%0AI%20know%20the%20kings%20of%20England%2C%20'
 + 'and%20I%20quote%20the%20fights%0AHistorical%0AFrom%20Marath'
 + 'on%20to%20Waterloo%2C%20in%20order%20categorical';

chain(badStr)
    .chain(decodeURIComponent)
    .chain(v => v.split('\n'))
    // .debug()                  // check the values along the way
    .chainMap(line => ({ line, length: line.length }))
    .chain(values => utils.table(values).render());

this can be more legible than the normal way to write this,
especially if you need to troubleshoot the value halfway through.

utils.table(
 decodeURIComponent(badStr)
   .split('\n')
   .map(line => ({ line, length: line.length }))
).render()

and it renders out a lovely table like this:

line length
I am the very model of a modern Major-General 45
I've information vegetable, animal, and mineral 47
I know the kings of England, and I quote the fights 51
Historical 10
From Marathon to Waterloo, in order categorical 47