DEV Community

Cover image for _a Redux like system: using ES6 Generators and vanilla JS ! :) - chapter 1
priyank_
priyank_

Posted on

_a Redux like system: using ES6 Generators and vanilla JS ! :) - chapter 1

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 a next 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,
}
*/
Enter fullscreen mode Exit fullscreen mode

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 = {};

}
Enter fullscreen mode Exit fullscreen mode

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");

Enter fullscreen mode Exit fullscreen mode

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"));

Enter fullscreen mode Exit fullscreen mode

Top comments (0)