Intro
Let's keep it simple, what does a redux like state model consist of ?
๐๏ธ A central state
๐๏ธ A message bus
๐๏ธ A function which updates the state based on the messages received via message bus
๐๏ธ A method on the state to get the current values
Roughly It works like this ๐ง ๐ง ๐ง
-
step 1
initialize the state with the starting values -
step 2
inform all the components in the environment about the message bus -
step 3
let components read values using the method available on the state -
step 4
let components send messages in an agreed-upon format using the bus and update the state
Now we can make any sophisticated state management system keeping the above four as our base building blocks
Also any primitive or a pre-baked language feature which can be used to achieve all the four above, can be used to create a Redux like system
Generators give us most of the above four
- Best thing about a generator is that it can be paused in between it's execution
- Just before the pause it has the ability to send the message to the scope, using the object produced by the generator function
- The scope using the generator object can unpause the generator and also can send a message back to it at the same time
Inner workings of a Generator
- when you call the Generator function it returns an object, semantically called an
iterator
, however Generator's function body is not executed on this call - The Generator's function body can execute via the
iterator
object - Generator's function body is able to communicate with the outer scope with the help of
yield
statements -
Iterator
object has anext
method which is used to unpause the Generator and execute it's function body
Little code snippet will help understand the basics of Generator
function Gen*(){
//1.
const foo = yield "message from the generator";
//2.
console.log(foo);
//3.
yield ({bizz: "we can return any valid type"});
};
const iterator = Gen();
/*
first call to the `next` method will
kickstart the execution of the Generator's
function body and automatically pause
at the first `yield` statement at line no 1.
Also before the pause the function body
will return the value guarded by the
`yield` statement. In the above case it is
"message from the generator" returned wrapped inside an
object. This object has the structure
{
value: <value guarded by the yield statement>,
done: <boolean value, indicating end of execution of
Gen function body>
}
*/
const message = iterator.next();
console.log(message);
/*
this will print an object
{
value:"message from the generator",
done: false
}
*/
/*
now we can unpause the generator function body with
another call to `next` on the `iterator` object.
Also on this call we can send a message
back to the Generator function body
as an argument to the `next` method.
*/
//4.
const message_from_iter =
iterator.next("message from the calling program");
//5.
console.log(iterator.next());
/*
The above line will pass the message back to the
`yield` statement at line no 1.
Which then is assigned to `const foo`
and printed at line no 2.
Then Gen function body pauses at line 3, after
passing the value guarded by the yield statement,
wrapped in an object. Structure of the is object
is again
{
value: { bizz: "we can return any valid type" }
done: false;
}
At line 4 it is assigned to `const message_from_iter`.
At line 5 we again call iterator.next to reach the end
of Generator's function body.
This time the object returned by the call is
{
value: undefined,
done: true,
}
*/
The above snippet is an introduction to Generator's working and represents sufficient enough Generator api which can be used to construct a Redux like system
Before we go any further lets go through the requirements again
๐๏ธ A central state
๐๏ธ A message bus
๐๏ธ A function which updates the state based on the messages received via message bus
๐๏ธ A method on the state to get the current values
lets address each requisite one by one using small code snippets
A Central State
We will use a POJO ( plain old javascript object ) to represent our state, declared inside the function body of the
generator
function* Gen(){
//1.
const centralState = {};
}
closure of the Generator's function body will preserve the state for the lifetime of the iterator
produced by calling the Generator function
A Message Bus
A message bus is a system of passing messages to and fro from the central state to the outside environment
We can use iterator.next()
to send the message in and yield
to send the message out from the Generator function body's scope. And these two api primitives will constitute the essential parts of our message bus
function* Gen(){
const centralState = {};
const messageIn = yield "message from the generator";
}
//1.
const iterator = Gen();
/*
line no 2. will kickstart the Generator
function body and pause at first yield
after receiving message from the generator
*/
//2.
const messageOut = iterator.next();
/*
line 3. will send the message back to
the Generator's function body scope at
the currently paused `yield` and gets
assigned to `messageIn` identifier
*/
//3.
iterator.next("message sent to Generator");
I present you with the code which explains the idea of how it can be done, in the next part of this article we look into the same code with some sugar syntax on top to make usage by the client code cleaner
function reducer (message, state){
const {type, value} = message;
let newState = {...state};
switch (type){
case "init_todo": {
newState = { ...newState, todoList: [] };
break;
}
case "add_item_todoList": {
newState = {...newState, todoList: [...newState.todoList, value]};
break;
}
}
return newState;
}
/*๐๐คก The generator function: the saviour*/
function* Gen(){
console.log("\n this message will print only on the first call to `iter.next`");
/*
* a simple state ๐
* */
let centralState = {};
/* ๐กfirst time when function halts at yield , it also returns this message to calling scope*/
const messageIn = yield "hi! i am the message from the generator: the first one";
/* ๐this is function the client code can use to read a given property on the state*/
const getStateSlice = (propertyName) => JSON.parse(JSON.stringify(centralState[propertyName]));
/* ๐using a plane function called reducer to process the incoming message and redefining the state */
centralState = reducer(messageIn, centralState);
/* ๐ using a loop like an event-loop to process the messages received */
while(true) {
const clientMessage = yield getStateSlice;
centralState = reducer(clientMessage, centralState);
}
}
//1.
const iterator = Gen();
/*
line no 2. will kickstart the Generator
function body and pause at first yield
after receiving message from the generator
*/
//2.๐ Initialze local variables and whatever you like on the first call to `iterator.next`
console.log("๐ For the first time `next` is called on the iterator object, we can use this call to initialize things");
const messageOut = iterator.next();
console.log(`message received ๐ฐ from the generator ${messageOut.value}`);
/* ๐ passing message using the iterator and getting a function in return which can read on the encapsulated state */
let {value: readProperty, done: _ } = iterator.next({ type: "init_todo", value:null });
console.log("lets see what we ๐ฐ get in return", readProperty("todoList"));
/* ๐ฎ adding a first value in our todo list where state is powered by a generator and then checking if it has been added */
let {value = readProperty, done = _ } = iterator.next({type: "add_item_todoList", value: "install headSpace"});
console.log("lets check again what did we ๐ฐ get in return", readProperty("todoList"));
Top comments (0)