Implementing State management library - Introduction to Awai
Tags: javascript, tutorial, webdev, opensource
Published: 2023-12-23 14:37:22

Hey JavaScript community :wave: It's been 7 months of intense thinking and tinkering, and Awai has finally reached release candidate stage.
In this tutorial, which will consist of multiple parts, I am going to show you how I came to certain ideas and how I implemented [Awai](https://github.com/yuriyyakym/awai) state management library, which is designed to extract business logics from UI layer, handle side effects and race conditions with ease, and declaratively describe application flow using async functions.

## What's the goal?

Throughout my career I've worked with many React projects and saw different approaches to state management. The worst, IMO is handling logics with React hooks, since it breaks [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) principle by mixing business logics with UI layer. Another problem is that all those hooks are dependant on each other, leading to redundant re-renders and stale props, which you have to solve with `useRef`, etc. I am pretty sure most of you faced these problems. But why do those problems exist at all? 
I hope, after reading all the parts which I plan to write here, you will see that state management can actually be very simple.

## How it works?

Fundamental part of Awai is thennable object, which plays role of event emitter. Thennable is an object which implements `then` method and can be used as a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

```ts
const thennable = {
  awaiters: [],

  then(onfulfilled) {
    this.awaiters.push(onfulfilled);
  },

  emit(value) {
    for (const resolve of awaiters) {
      resolve(value);
    }
  }
};
```

As soon as it is used as a promise, `then` method is called receiving resolving callback as an argument. The callback should be called when thennable resolves.

For example, if you write `await thennable`, the callback resolving this `await` will land in `awaiters` array of our object, and it's up to us when to call it.

Same as we can await promise multiple times, I've tried to do it with custom thennable, and this is how I came to an idea of re-resolving the same thennable object multiple times.

Once classic JS promise is settled, it resolves with the settled value as soon as you await it again. With custom implementation we resolve all the awaiters, and cleanup the array, making further subscriptions to thennable possible without resolving them right away.

Now let's create a class that creates thennable re-resolvable event. It will be named `AwaiEvent` to be aligned with naming inside of the library.

```ts
class AwaiEvent {
  #awaiters = [];

  then(onfulfilled) {
    this.#awaiters.push(onfulfilled);
  }

  emit(value) {
    const awaiters = this.#awaiters;
    this.#awaiters = [];

    for (const resolve of awaiters) {
      resolve(value);
    }
  }
}
```

This is how thennable can resolve with different values:

```ts
const event = new AwaiEvent();

setTimeout(() => event.emit('hello'), 100);
setTimeout(() => event.emit('awai'), 200);

const value1 = await event;
const value2 = await event;

console.log(`${value1} ${value2}`); // hello awai
```

So exciting! We can now use this event in order to replace classical event emitters. How? Just await it in the loop 😎. You can also combine this event with other promises using `Promise.race`, `Promise.all`, etc.

```ts
const event = new AwaiEvent();

while(true) {
  const value = await event;
  console.log(event);  
}
```

Any time you call `event.emit(...)` the emitted value will be logged.

By the way, this is exactly how event listening was implemented in the early versions of Awai: [GitHub commit](https://github.com/yuriyyakym/awai/commit/603a2d558b9d7dd66be02839b037efe9ba7e6048#diff-2584df5715c451bf392b6582e66b78d62f0f73074d7f84879cc1592d4c4c1f0fR4-R12) 😄.
Right now this util is called `scenario`, which has drastically evolved, and can be used in a variety of different ways. But let's not cover its functionality in this part. Nevertheless, feel free to check [the docs](https://awai.js.org/scenario).

## How is state node implemented?

State is a very simple object, which has `get`. & `set` methods, and `events` property. `get` method returns a current value. `set` method updates the value inside of the object and emits the thennable event we created above. Have a look at the basic state node implementation:

```ts
const state = (initialValue) => {
  let value = initialValue;

  const events = {
    changed: new AwaiEvent()
  };

  const get = () => value;

  const set = (newValue) => {
    value = newValue;
    events.changed.emit(newValue);
  };

  return { events, get, set };
};
```

Easy, right? Now, if we create a state node, we can react to its events, and this is a great start for a state management library.

## How to connect this weirdo to React?

Let's write a very simple React hook, which listens to every emit and updates React state once our event is emitted.

```ts
const useStateValue = (stateNode) => {
  const initialValue = stateNode.get();
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    let mounted = true;

    const listen = async () => {
      while (mounted) {
        const newValue = await stateNode.events.changed;

        if (mounted) {
          setValue(newValue);
        }
      }
    };

    listen();

    return () => {
      mounted = false;
    };
  }, [stateNode]);

  return value;
};
```

Finally we can use our so far primitive state manager library with React:

```tsx
const counterState = state(0);

const increment = () => {
  counterState.set(counterState.get() + 1);
};

const Counter = () => {
  const counter = useStateValue(counterState);

  return (
    <div>
      <p>{counter}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};
```

## Summary

I hope you enjoyed the reading. Happy to answer your questions. See you in the next parts.

Here you can find a playground with the example we have created together: [CodeSandbox](https://codesandbox.io/p/sandbox/awai--article-part1-example-52vnsj?file=%2Fsrc%2FApp.js%3A8%2C3).