The Reactor Pattern
Sam Holmes
Posted on March 11, 2020
The Reactor Pattern is a way to achieve a reactive-programming style of coding with the use of a primitive called a Reactor. Similar to how Promises are a primitive concerned with asynchronous control-flow, Reactors are proposed to be a primitive concerned with the data streams and the propagation of change.
What are Reactors
Reactors are objects that maintain an internal state and implement two methods to retrieve and update the state. Along with this, they maintain two arrays of other Reactor objects: an ancestors array and descendants array. These two arrays act as data-binding relationships between Reactors and establish a graph of data propagation:
A Reactor should invoke the update methods for each of its descendants after its internal state has been changed. Each descendant should have access to the state from its ancestors when being updated. This allows for automatic propagation of change throughout the graph.
How to use Reactors
Let's discuss how to use a specific implementation of a Reactor.
const myReactor = new Reactor(initialState);
In this example a Reactor is instantiated from the Reactor class. The state of the Reactor is defined by the initialState
constructor argument. The initialState is optional and can be omitted, but by doing so the Reactor's internal state is undefined
.
With this specific implementation, state can be accessed and redefined using the state getter/setter:
myReactor.state = newState;
console.log(myReactor.state);
The getter/setter internally invokes the the retrieve and update methods.
State can be defined as an expression by using a function as the state argument.
const myReactor = new Reactor(() => 1 + 1);
This allows you to define a Reactor's state as an expression of other Reactor's state.
const noun = new Reactor('world');
const message = new Reactor(() => `Hello ${noun.state}`);
The implementation will automatically link the message
Reactor to the noun
Reactor by adding noun
to the ancestor array in message
and message
to the descendants array in noun
. Because this relationship is established, the Reactor class will know to invoke message.update()
when noun
changes its internal state.
const noun = new Reactor('world');
const message = new Reactor(() => `Hello ${noun.state}`);
console.log(message.state); // "Hello world"
noun.state = 'Reactors';
console.log(message.state); // "Hello Reactors"
Dynamic Graphs
The state of the Reactor graph is not static, but rather dynamic. This means that the ancestors for a Reactor is not fixed throughout the life of the Reactor.
const hungry = new Reactor(true);
const food = new Reactor('pancake');
const drink = new Reactor('milk');
const consuming = new Reactor(() => hungry.state ? food.state : drink.state);
In this example, consuming will always have two ancestors: hungry and either food or drink depending on the state of hungry. If hungry is "truthy", then consuming will be linked to changes of food, otherwise it will be linked to changes of drink. The structure of the Reactor graph can change during propagation of state changes.
This dynamism of the graph means that Reactors typically are optimized to react to only the changes which concern them.
Side-effects
A Reactor can be pure with no side-effects if all state within a Reactor's expression function is composed of constants or other Reactors.
const pure1 = new Reactor(() => 23);
const pure2 = new Reactor(() => pure1.state * 2);
const pure3 = new Reactor(() => pure1.state + pure2.state);
Pure Reactors only have access to variable (changing) state that is from other Reactors; only from state within the Reactor graph. In addition, A pure Reactor cannot have side-effects:
// Accessing state outside of the graph is not pure
const notPure1 = new Reactor(() => document.title);
// Producing side-effects is placing state outside of the graph and is not pure.
const notPure2 = new Reactor(() => console.log(otherReactor.state));
Although perfectly legal and legitimate, creating impure Reactors should be handled with care. It is recommended to place these Reactors towards the tips of the Reactor graph. Doing so will improve the dependability of your applications.
Conclusion
Reactors are an awesome new tool at the developer's disposal. It can supplement an existing application's state management needs and it can also act as a replacement for all state management. This is only the beginning for this pattern. I'm excited to see where the community will take this pattern in the future!
Further Reading
- Arc Reactor – A basic implementation of the Reactor Class
- IronJS – A New Application Framework Leveraging The Reactor Pattern
Posted on March 11, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.