Skip to content

Process

Module Process

There are times where several lines of code are used to process data. Doing a series of transformations to massage into some shape. For example:

const p = (input) => {
input = input.toUpperCase(); // Convert to upper-case
input = input.at(0) === `A`; // Set to true/false if first letter is an 'A'
return input; // Return true/false
}
p(`apple`); // true

This is a perfectly fine bit of code to write, but you’ll notice that the variable being manipulated - input - is repeated continually. This seems to violate the DRY principle (Don’t Repeat Yourself).

Since we assign it different data types, it’s also difficult at any point to be sure what data type it is. An alternative is to use intermediate variables:

const p = (input) => {
const toUpper = input.toUpperCase();
const isFirstLetterA = toUpper.at(0) === `A`;
return isFirstLetterA;
}
p(`apple`); // true

The advantage of this approach is we have more literate code, and we (or our code editor) can be more sure of what each data type is. However it seems wasteful to have to come up with names for all the steps.

We’d really just like to articulate the steps of each processing.

One of the patterns ixfx offers for doing this is Process.flow , which creates a flow of operators (up to 5 are supported). An operator is just a function that takes a value and returns a value.

The flow is encapsulated in a function that accepts an input value and returns an output.

import { flow } from 'https://unpkg.com/@ixfx/process/bundle';
// Create a flow that first converts to upper case, and then
// returns true/false if first letter is a capital A
const p = flow(
value => value.toUpperCase(),
value => value.at(0) === 'A'
);
p('apple'); // _true_

This may not seem too much of an advantage over the original. We still have value repeated, for example. Note how we don’t really pass arguments to the functions, Process.flow takes care of that.

However we’re now composing a series of operators as an array, rather than writing lines of code. This makes it easy at editing time to re-arrange the order if we need to, and the list can also be dynamically constructed and manipulateed t run time too.

Another advantage is that operators can be defined, well-tested and re-used instead of ‘reinventing the wheel’.

A few pre-defined operators are available, see the API docs for full details

Math

Etc

  • Process.rank - returns the ‘best’ value seen so far based on a provided ranking function. This is like min/max but works for objects.

Temporal

Logic

The flow can produce effects, for example:

import * as Process from 'https://unpkg.com/@ixfx/process/bundle';
const p = Process.flow(
// Only ever output the largest-seen value
Process.max(),
// If value has not changed, output _undefined_
Process.seenLastToUndefined(),
// Only do something if not undefined,
// ie. we have a new max value
Process.ifNotUndefined(v => {
console.log(`v:`, v);
})
);
p(100); // Prints 'v:100'
p(90); // Nothing happens, max value has not changed
p(110); // Prints 'v:110'

If you want to process data from an event, define it once, and then use it within the event handler:

const coordFlow = Process.flow(
value => ({ x: value.x, y: value.y }), // Pluck just x,y
value => Points.divide(value, window.innerWidth, window.innerHeight) // Make relative to viewport
)
document.addEventListener(`pointermove`, event => {
const relativePoint = coordFlow(event);
// Gets a relative { x, y }
})

Likewise if you wanted to process data from an iterable or generator, use it within a for of loop or similar. This technique allows us to share the same data processing flow in different parts of the code.

You can add conditional logic using the ternary operator.

In the below example, we check for the letter ‘A’ if the seconds past the minute is less than 30, otherwise check for ‘B’.

import { flow } from 'https://unpkg.com/@ixfx/flow/bundle';
const p = flow(
value => value.toUpperCase(),
// If seconds past the minute...
new Date().getSeconds() < 30 ?
value => value.at(0) === 'A' : // ...is 0-29
value => value.at(0) === 'B' // ...or 30-59
);
// Sometime we'll check for 'A', sometimes 'B', depending on time
p('apple');