E~wee~ctor: writing tiny Effector from scratch #1 — Counter
Tags: effector, graphs, javascript
Published: 2020-04-01 21:11:15

Hi, all!

I really like _"Implement something from scratch"_ type of articles, for example:

- [Lost with Redux and sagas? Implement them yourself!](https://blog.castiel.me/posts/2019-08-03-lost-redux-saga-reimplement-them/)
- [Building Redux from scratch](https://medium.com/@guokai83524/building-redux-from-scratch-e12eb0e484c8)
- [Build your own React](https://pomb.us/build-your-own-react/) – _this one is particularly awesome_

It is a good way to learn how things work, and to unveil magic behind the black box of a library.

I plan to write a small [Effector :comet:](https://github.com/zerobias/effector)-like library called **E<i>~wee~</i>ctor**. This will be an educational-only purpose project.

- Name "eweector" is derived from "effector" and "wee", meaning "little, small, tiny"
- Second meaning of "wee" is also fit
- Also my name is Victor and I like how "eweector" sounds :)

- - -

Good and simple place to start is a "counter" example from [Effector website](https://effector.now.sh/):

```javascript
import {createStore, createEvent} from 'effector'

const add = createEvent()
const sub = createEvent()
const reset = createEvent()

const counter = createStore(0)
  .on(add, (count, n) => count + n)
  .on(sub, (count, n) => count - n)
  .reset(reset)

counter.watch(n => console.log('counter:', n))
// counter: 0
add.watch(n => console.log('add', n))
sub.watch(n => console.log('subtract', n))
reset.watch(() => console.log('reset counter'))

add(5)
// add 5
// counter: 5
sub(1)
// subtract 1
// counter: 4
reset()
// reset counter
// counter: 0
```

Thus we need to implement two main entities – `event` and `store` – and some of their methods.

I will not dive deeply into Effector right now, if you want to do it, check [this article](https://dev.to/yumauri/effector-we-need-to-go-deeper-4geg) out. But here are some highlights from Effector's architecture:

- Effector internally creates a graph of nodes
- Nodes has (obviously) links to next nodes = edges of the graph
- Any node can contain sequence of steps to run
- A step can modify input data somehow, or stop branch computation, or launch some side-effect function
- Upon some event Effector's kernel passes input data to a node and starts executing steps, using the [breadth-first search algorithm](https://en.wikipedia.org/wiki/Breadth-first_search)
- Any high-level Effector's unit ([Event](https://effector.now.sh/docs/api/effector/event), [Store](https://effector.now.sh/docs/api/effector/store), [Effect](https://effector.now.sh/docs/api/effector/effect), [Domain](https://effector.now.sh/docs/api/effector/domain)) is a some object, attached to a node or bunch of nodes.

Here is the logical relationship graph for the code above:

![Relationship Graph](https://dev-to-uploads.s3.amazonaws.com/i/qwom4ghbnonmkwbwchr3.png)

And here is what Effector creates, structural graph of nodes:

![Structural Graph](https://dev-to-uploads.s3.amazonaws.com/i/sai824aixw7c1k2fy1kn.png)

# Node

Let's start with the core thing of a graph – a _node_. A node should contain links to the next nodes – edges of a graph, and sequence of _steps_ – where we will describe logic.

```javascript
export const createNode = ({ next = [], seq = [] } = {}) => ({
  next,
  seq,
})
```

`createNode` factory function is very simple, it just creates a node object and nothing else. We could even go without it, and describe nodes in-place, like plain objects. But factory function gives us two benefits:

- Each and every node it returns has same structure, so we will not miss some fields, defining them manually
- ES6 syntax has very handy [default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) and [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), and they can take burden of assigning default field values for node

# Steps

Next thing we need is _steps_. There are six types of steps in Effector, but we will start from a single one. And we will not even create factory for it :) Let's just use _function_ as a step. So out nodes will contain a sequence of functions to execute.

# Event

_Event_ is the simplest Effector unit. It is a function, attached to a graph node. The only purpose of an event as a high-level entity is to place input data into the node and to start computation cycle.

```javascript
export const createEvent = () => {
  const event = payload => launch(event, payload)
  event.graphite = createNode()
  return event
}
```

`createEvent` factory creates function and attaches graph node into its `graphite` field. Created _event_-function accepts input data and executes function `launch`. This `launch` function will start a computation cycle, and we will describe it later, when we write a kernel.

As you can see, a node, attached to an event, doesn't have any _next_ nodes, nor _steps_.

# Watch

Before writing a kernel, let's implement _watch_ functionality.

_Watch_ is an auxiliary node, which runs side-effect. In case of our counter example this side-effect is _console.log_.

```javascript
export const watch = unit => fn => {
  const node = createNode({
    seq: [fn],
  })
  unit.graphite.next.push(node)
}
```

`watch` function accepts two arguments (in a functional programming meaning) – `unit` to attach `watch` node, and function to execute.

As we decided to use simple functions as steps (for now) – we will just use given side-effect function as a step for `watch` node.

And after creating `watch` node, we put this node to the `next` array for the given unit.

![Watch](https://dev-to-uploads.s3.amazonaws.com/i/86wpsrxw2spmdqjytkhr.png)

And now let's add `.watch` method to our _event_:

```diff
export const createEvent = () => {
  const event = payload => launch(event, payload)
  event.graphite = createNode()
+  event.watch = watch(event)
  return event
}
```

So we will be able to watch events:

```javascript
const event = createEvent()
event.watch(data => console.log(data))
```

# Kernel

And here we go :) The _kernel_. It is not that frightening as it sounds, really.
There are two main parts of the kernel: _queues_ and _computation cycle_.

Effector utilises five queues. We will start with single one:

```javascript
// contains objects { node, value }
const queue = []
```

Computation cycle traverses graph and executes each step from each node:

```javascript
const exec = () => {
  while (queue.length) {
    let { node, value } = queue.shift()
    node.seq.forEach(step => (value = step(value)))
    node.next.forEach(node => queue.push({ node, value }))
  }
}
```

In simple words function `exec` can be described as following steps:
1. While `queue` is not empty, take element from queue
2. Execute each step from node, with initial value from queue element
3. Put each node from `next` array to the queue, with new value
4. Go to 1.

One more thing – we need function `launch` for our events:

```javascript
export const launch = (unit, value) => {
  queue.push({ node: unit.graphite, value })
  exec()
}
```

`launch` function just puts _node_ and _value_ into the queue, and starts computation cycle. Thats it.

# Store

And last, but not least – a _store_.

```javascript
export const createStore = defaultState => {
  let currentState = defaultState
  const store = {}

  store.graphite = createNode({
    seq: [value => (currentState = value)],
  })

  return store
}
```

`createStore` factory creates object, and attaches graph node into its `graphite` field, just like with an event. But _store_ node has one step – it saves input data into an enclosured variable `currentState`.

We also need to implement few store's methods, like  `.on`, `.reset` and `.watch`. Let's start with last one:

```javascript
  store.watch = fn => {
    fn(currentState)
    return watch(store)(fn)
  }
```

`.watch` method for store is a bit different, than for an event – first time it is called it executes given function with current state, and then creates `watch` node.

```javascript
  store.on = (event, fn) => {
    const node = createNode({
      next: [store.graphite],
      seq: [value => fn(currentState, value)],
    })
    event.graphite.next.push(node)
    return store
  }
```

`.on` method accepts an event (or any unit) and reducer function. Like `.watch` method it creates a new `on` node, with one step, where reducer is called. And places this new node _before_ `store` node in the graph, so new value from reducer will be saved in the `store` node. Also it puts this new node to the `next` array for the given event (or unit).

![On](https://dev-to-uploads.s3.amazonaws.com/i/r99e46k2cg50bg0xz6qq.png)

```javascript
  store.reset = event => store.on(event, () => defaultState)
```

`.reset` method is just shortcut to set initial state.

- - -

And we've done our first step in this journey. If we combine all these pieces together, we will get minimal working "eweector", which could run counter example. And what is more important – it follows Effector's architecture!

In next chapters we will grow up our infant. I'll try to cover all Effector's API in reasonable limits, so stay tuned ;)

I've created [project on GitHub](https://github.com/yumauri/eweector) to help you follow the source code.
All code, described in this chapter, is committed in [this commit](https://github.com/yumauri/eweector/commit/1be38ae64887d4735dc61cb7230f3b0eba50df10).

Thank you for reading!