Nanoflux Fusion is an evolution of Nanoflux that adopts the Redux approach using reducer functions (I call them Fusionators) to change the state inside the one and only store.
First of all, using Fusion you won’t lose any of the original functionality, but you’ll gain a very comfortable way to deal with application state. While the traditional approach uses Actions, Dispatcher(s) and Stores, the evolved Fusion reduces the architecture to several Actions, which I call Actors in this case, and a single Store. Although, the Dispatcher still exists, the user won’t get in touch with it, as it is a hidden implementation detail. The main difference is, that there’s no need to implement the Store, as the state manipulation is done within the assign/merge/fuse/reduce functions (named Fusionators), which are handled by the dedicated Fusion store. The Fusion interface provides also a special action creator, which returns action functions (named Actors).
Fusionators are implemented by the user and defines the logic how state will change. A Fusionator is a function that receives the previous state and the action arguments and returns the new state. While this grows proportionally to applications size, it is possible to break the logic in multiple Fusionators.
Actors are called like normal functions with any kind of parameter. When a Fusionator is created, the actors are constructed also and are available via getFusionActor()
With the growth of the project a single Fusionator would become quite large, and it could be cumbersome to find adequate actor names. Fortunately, Fusion is capable to support multiple Fusionators. Each Fusionator has its own namespace, avoiding naming conflicts though.
If namespace is not given (good for simpler scenarios) the default namespace is used.
Fusion supports asynchronous actions out-of-the-box. If a Fusionator returns a promise instead of a state object, the promise will be executed, i.e. action is asynchronous. The state shall be passed as argument of the resolver. Chaining is also possible. Fusion aims to support all A+ compliant implementations. It is currently tested with the
function asyncA(arg1){
return new Promise(function(resolve,reject){
setTimeout(function(){
// returns state to be mergeds
resolve({a: arg1});
}, 500)
})
}
function asyncB(arg){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve( {b: 5 + arg.a} );
}, 500)
})
}
var asyncFusionator = NanoFlux.createFusionator({
simplePromise: function(prevState, args){
return asyncA(args[0]);
},
chainedPromises: function(prevState, args){
return asyncA(args[0]).then(function(data){
console.log(data); // data = {a: 5}
return asyncB(data);
});
}
},
// initial state
{
a:0,
b:0
});
var simplePromise = NanoFlux.getFusionActor("simplePromise");
var chainedPromises = NanoFlux.getFusionActor("chainedPromises");
// call the actions
simplePromise(5); // state will be { a: 5 }
chainedPromises(5); // state will be { a: 5, b: 10 }
Fusion provides a simple middleware interface to apply generic functionality before states are merged into the application state.
The Fusion Store
’s use
method accepts a function of the following structure
const middlewareFunction = function (newState, currentState, actionName){
// ... your implementation
return newState;
}
The newState
argument is the state object returned from the Fusionator, while the currentState
is the most recent application state. Obviously, actionName
is the name of the triggered action (that one passed on getFusionActor
.
The middleware functions are called in the order as they are added to the store.
Note: Nanoflux also provides a middleware interface (
Nanoflux.use()
), but that interface applies for the dispatcher. In case of Fusion, the dispatcher middleware isn’t that useful, as it dispatches always to the same method (i.e. on__fuse()) with internally maintained arguments.
function LoggerMiddleware(){
var logData = [];
this.log = function(newState, oldState, actionName){
logData.push({
action: actionName,
timestamp: Date.now(),
state: _.cloneDeep(oldState)
});
return newState; // must return a state
};
this.countLogEntries = function(){ return logData.length };
this.getLogEntry = function(t){
return logData[t];
};
}
var fusionStore = NanoFlux.getFusionStore();
var logger = new LoggerMiddleware();
fusionStore.use( logger.log );
Each middleware must return a state object, usually the newState
itself.
But it can be also a modified version of newState
; this way, you can build a kind of a (generic) transformation pipeline.
function addTimestamp(newState, oldState){
var modifiedState = {};
modifiedState.modified = Date.now(); // adds a timestamp to the state
Object.assign(newState, modifiedState);
return newState;
}
fusionStore.use( addTimestamp );
There’s no support for asynchronous middleware functions yet, that means that the middleware execution doesn’t wait for asynchronous operations.