Command
Sinux use a Signal Command architecture. When a Signal is dispatched all registered command for this Signal is executed. The order the commands are executed is FIFO (first in first out).
Command are by definition a method executed when a signal is dispatch.(see Signal)
Store, Signal and Command work together in order to mutate Store state.
Here is a simple example of how to create a Store then attach a Command to a store's Signal and execute them.
const myStore = new Store( {}, 'mutate');
myStore.mutate.add( (state, args) => { return {...state, ...args} });
myStore.mutate({ foo: 'bar'}).then((state) => console.log(state));
// {foo: 'bar'}
As explain in Store section, Signal attached to a Store are turned to an handy method that dispatch the Signal injecting the Store state as first parameter. When the dispatch method is resolve (using Promise) the updateStore
method is called with the returned object of the command execution.
Here is a extended example of what's happen when invoking these "shortcut" methods.
const myStore = new Store( {}, 'mutate') // create store
myStore.mutate.add( (state, args) => { return {...state, ...args} }); // add command to store's signal
myStore.mutate.dispatch(myStore.getState(), args) // dispatch signal
.then((result) => myStore.updateState(result))
.then((state) => console.log(state));
As explained in the Store section, updateState
method will not override the state.
myStore.mutate.add( (state, args) => { return {...state, ...args} });
// is equals to
myStore.mutate.add( (state, args) => { return {...args} });
Multiple commands
You can add multiple Command to a Signal. Command are executed in a FIFO queue. The result applied to the Store state will be the composition of all the results returned by the Command.
const myStore = new Store( {a: 1}, 'mutate') // create store
myStore.mutate.add( (state) => { return {foo:'bar'} });
myStore.mutate.add( (state) => { return {foo:'beer'} });
myStore.mutate.add( (state) => { return {hello:'world'} });
myStore.mutate().then((state) => console.log(state));
// { a: 1, foo: 'beer', hello: 'world'}
Asynchronous Command
In order to write asynchronous code in a Command simply return a Promise or a async/await / generator function.
Using a Promise
const myStore = new Store( {}, 'mutateAsync') // create store
myStore.mutateAsync.add( (state, args) => new Promise((resolve) => {
resolve({...args})
})); // add command to store's signal
myStore.mutate({ foo: 'bar'}).then((state) => console.log(state));
WARNING: When using async function or generator function the command should return the execution of the function not the function. Simply add
()
after the function.
Using async/await function
const myStore = new Store( {}, 'mutateAsync') // create store
myStore.mutateAsync.add(async function(state, args) {
const result = await [whatever async function call you want];
return {...result};
}); // add command to store's signal
myStore.mutate({ foo: 'bar'}).then((state) => console.log(state));
Using a Generator function
const myStore = new Store( {}, 'mutateAsync') // create store
myStore.mutateAsync.add( function* (state, args) {
const result = yield [whatever async function call you want];
return {...result};
}); // add command to store's signal
myStore.mutate({ foo: 'bar'}).then((state) => console.log(state));
Before v0.2.0
Commands are object that connect signal with listener.
A command will be executed every time a signal is dispatched.
Creating a command
const testSignal = new Signal('test');
new Command(testSignal, (text) => {
return text.toUpperCase();
});
// invoke command
testSignal.dispatch('bob')
.then((upperText) => console.log(upperText));
// output: BOB;
Here is a simple example and creating a command is similar as adding a listener on a signal.
Command and Store
Command and Store work together in order to manage state mutation.
When a store signal is dispatched, the store listen for all listeners to be executed (sync and async listener) then it update its state and dispatch a changed
signal.
When creating a command using a Store signal, the first parameter will always be the current state of the store.
const myStore = new Store({}, 'action','action2');
new Command(store.action, (state, ...args) => {...state, ...args} )
store.action({foo:'bar'}).then(()=> console.log(store.getState()))
// output : {foo:'bar'};
Command can be asynchronous
Signal listener can be asynchronous
- Promise style
new Command(store.action, (state, ...args) => {
return new Promise((resolve, reject) => {
store.action2(...args)
.then((response) => resolve(response))
.catch((e) => reject(e));
})
});
- Generator function style
new Command(store.action, (state, ...args) => {
return function *(){
let r = yield store.action2(...args)
return r
}()
});
- Generator function style
new Command(store.action, (state, ...args) => {
return async function (){
let r = await store.action2(...args)
return r
}
});
Note: Calling other store signal is easy. This allow to wait for another store to be updated before responding.
Asynchronous command allow to call web services and/or perform any asynchronous code and resolve when action is performed.