# Functional Singletons in TypeScript With Real Use Cases

Singletons are commonly used in Object Oriented Programming when we want to enforce that there is only ever a single instance of a class. This might be because we are trying to encapsulate some global state between processes.

In this article, I will use the queue from my [es-reduxed](https://github.com/Antman261/es-reduxed) package. The purpose of this queue is to:

* Store a list of unprocessed event ids
* Maintain the correct order of events
* Ensure an event is only processed once

These assurances must be made because there is no guarantee that the events will be sent from the subscription only once and in order. While the system is processing one event, it may receive several more.

As you can guess, if we were to wind up with two instances of the event queue, we could no longer guarantee order and single processing constraints.

Let's take a look at the queue itself, starting with the types:

```typescript
export type Queue = {
  enqueue: (id: number) => void;
  registerPromise: (id: number, resolver: PromiseResolver) => void;
};

export type PromiseResolver = (value: Store<any, any>) => void;
```

So we can see a `Queue` is just an object with two properties:

* `enqueue`: a function taking a `number` and returning `void`
* `registerPromise`: a function taking a number and a function

The `registerPromise` function is just associating a resolve function from a Promise with an event id so that the queue can resolve the promise with the given redux state when it finishes processing that event. 

Let's take a look at the queue implementation:

```typescript
/**
 * This queue system uses a recursive loop and a primitive state machine to
 * ensure that events are dispatched to redux in exactly the order they were
 * received.
 */
const startQueue = <T extends EventBase>(
  reduxStore: Store<any, any>,
  eventsRepo: EventsRepo<T>
) => {
  const queue: number[] = [];
  const dedupeSet = new Set<number>();
  const promiseMap = new Map<number, PromiseResolver>();
  let state: 'READY' | 'PROCESSING' = 'READY';

  const processEvent = (event: EventBase) => {
    reduxStore.dispatch(event);
    if (event.id === undefined) {
      throw new Error(`Malformed event is missing id: ${event}`);
    }
    const resolver = promiseMap.get(event.id);
    if (resolver) {
      resolver(reduxStore.getState());
      promiseMap.delete(event.id);
    }
  };

  const processQueue = async () => {
    if (state === 'READY') {
      queue.sort((a, b) => a - b);
      const eventId = queue.shift(); // So we only process if something was in the queue
      if (eventId) {
        state = 'PROCESSING';
        if (queue.length) {
          // More than one event in queue, so do bulk processing
          const lastEventIndex = queue.length - 1; // Save queue length in-case it changes during the await
          const lastEventId = queue[lastEventIndex];
          const events = await eventsRepo.getEventRange(eventId, lastEventId);
          events.forEach(processEvent);
          queue.splice(0, lastEventIndex + 1);
        } else {
          const [event] = await eventsRepo.getEvents(eventId - 1, 1);
          processEvent(event);
        }
        state = 'READY';
        processQueue();
      }
    }
  };

  return {
    enqueue: (id: number | string) => {
      const idCoerced = typeof id === 'string' ? parseInt(id, 10) : id;
      if (!dedupeSet.has(idCoerced)) {
        dedupeSet.add(idCoerced);
        queue.push(idCoerced);
        processQueue();
      } else {
        console.warn(`Out of order event: [${idCoerced}]`);
      }
    },
    registerPromise: (id: number, resolve: PromiseResolver) => {
      promiseMap.set(id, resolve);
    },
  };
};
```

Okay, let's break this down:

```typescript
const startQueue = <T extends EventBase>(
  reduxStore: Store<any, any>,
  eventsRepo: EventsRepo<T>
) => {
```
The first thing to notice here is that we do not export this function, `startQueue` is available only inside the `queue.ts` module. This is a critical point we will come back to later.

```typescript
  const processEvent = (event: EventBase) => {
    reduxStore.dispatch(event);
    if (event.id === undefined) {
      throw new Error(`Malformed event is missing id: ${event}`);
    }
    const resolver = promiseMap.get(event.id);
    if (resolver) {
      resolver(reduxStore.getState());
      promiseMap.delete(event.id);
    }
  };
```

This function is defined inside `startQueue`, so it is only available within the `startQueue` function. This is similar to a private method. However, it has access to all variables included in its closure. In this case, we are making use of `promiseMap` and `reduxStore`. This is similar to private properties in a class, but we use closures to make them inaccessible outside this context.

```typescript
  const processQueue = async () => {
    if (state === 'READY') {
      queue.sort((a, b) => a - b);
      const eventId = queue.shift(); // So we only process if something was in the queue
      if (eventId) {
        state = 'PROCESSING';
        if (queue.length) {
          // More than one event in queue, so do bulk processing
          const lastEventIndex = queue.length - 1; // Save queue length in-case it changes during the await
          const lastEventId = queue[lastEventIndex];
          const events = await eventsRepo.getEventRange(eventId, lastEventId);
          events.forEach(processEvent);
          queue.splice(0, lastEventIndex + 1);
        } else {
          const [event] = await eventsRepo.getEvents(eventId - 1, 1);
          processEvent(event);
        }
        state = 'READY';
        processQueue();
      }
    }
  };
```

Here we continually process the queue in a recursive loop, as long as there are events remaining and the queue is not already processing events. This ensures we only process events once. Because this function is `async` (it returns a promise), and because it will always call `await` before it recursively calls `processQueue` again, this function will not lock the event loop.

It's also important to realise that there might be calls to `enqueue` during the `await` step. This would grow the queue, but not be included in the call to `getEventRange`, so this is why we save the last event index before we `await`; otherwise, we could accidentally splice out events we hadn't processed yet.

Now that we understand the queue's "private methods" and properties, let's take a look at the "public methods" in the return statement:

```typescript
  return {
    enqueue: (id: number | string) => {
      const idCoerced = typeof id === 'string' ? parseInt(id, 10) : id;
      if (!dedupeSet.has(idCoerced)) {
        dedupeSet.add(idCoerced);
        queue.push(idCoerced);
        processQueue();
      } else {
        console.warn(`Out of order event: [${idCoerced}]`);
      }
    },
    registerPromise: (id: number, resolve: PromiseResolver) => {
      promiseMap.set(id, resolve);
    },
  };
```

Aha! So when we enqueue an event, we call `processQueue` if it is an event we haven't seen before. Remember the state checks in `processQueue`? We can safely call this function here because it won't do anything if there is already a process running, and it will eventually get to our enqueued event through the recursive loop.

Meanwhile, `registerPromise` maps a promise to the event id, which will be used later. In this implementation, we only allow resolving one promise per event processed.

Let's get to the meat of this article, the function that instantiates or retrieves this queue as a singleton:

```typescript
export const getQueue = (() => {
  let instance: Queue;
  return <T extends EventBase>(
    reduxStore: Store<any, any>,
    eventsRepo: EventsRepo<T>
  ) => {
    instance =
      instance === undefined ? startQueue<T>(reduxStore, eventsRepo) : instance;
    return instance;
  };
})();
```

First, take note of the fact that this is the only run-time export from `queue.ts`. You can _only_ retrieve a queue by calling `getQueue`, but what's actually happening here?

```typescript
export const getQueue = (() => {
 // Trimmed
})();
```

Above is an immediately invoked function expression. We are defining a function and then calling it. The result of this function is then assigned to the variable `getQueue`. Well, `getQueue` is a function -- we can tell because the first word is a verb -- so this immediately invoked function expression needs to return a function.

```typescript
(() => {
  let instance: Queue;
  return // Trimmed
})();
```

Before it returns, we declare a mutable variable called `instance` of the type `Queue` but do not define a value for it, which means it will be undefined.

```typescript
(() => {
  let instance: Queue;
  return <T extends EventBase>(
    reduxStore: Store<any, any>,
    eventsRepo: EventsRepo<T>
  ) => {
    // Trimmed
  };
})();
```

Aha! So after we declare our `instance` variable for storing a queue, we define a function for our immediately invoked function expression to return. This function signature should look familiar -- it is identical to `startQueue`'s function signature.

```typescript
(() => {
  let instance: Queue;
  return <T extends EventBase>(
    reduxStore: Store<any, any>,
    eventsRepo: EventsRepo<T>
  ) => {
    instance =
      instance === undefined ? startQueue<T>(reduxStore, eventsRepo) : instance;
    return instance;
  };
})();
```

The final step: Inside the function returned to `getQueue` by our immediately invoked function expression, we will: If `instance` is undefined, call `startQueue`, passing in our generic type parameter, `reduxStore`, and `eventsRepo`, OR, in the case that it is defined, we simply leave `instance` as `instance`. Then we return `instance`.

This means that `instance` is stored in the closure scope of `getQueue`. It is no longer accessible anywhere in javascript except by `getQueue` itself. We can be confident that `getQueue` will only ever return a single queue instance. The first time it instantiates the queue, and subsequent calls return it.

You can test this fact by asserting:

```typescript
expect(getQueue()).to.equal(getQueue());
```

This will return true because both calls return a reference to the same object!

```typescript
console.log(getQueue() === getQueue()); // true
```

Remember object equality in javascript compares references.

There's a massive caveat in this implementation; can you spot it? *We only use the parameters the first time `getQueue` is called!* This means that if I try to start one queue for one store and another queue for another store, it will simply ignore the second store and return my queue for the first store.

In this way, this pattern is *not* memoization. If we were using memoization, we would be able to create a singleton per combination of `reduxStore` and `eventsRepo`. However, this makes it harder to enforce a singleton pattern. What defines whether `reduxStore` and `eventsRepo` are the same as the last call? Would we compare object equality? Deeply nested properties?

For example, if I were to use a proxy-based memoization implementation such as [proxy-memoize](https://www.npmjs.com/package/proxy-memoize) then changes to properties of, or child properties of `reduxStore` and `eventsRepo` would cause calls to `getQueue` to return a new queue instance! Uh oh!

I like to think of this pattern as *brutal memoize*. You get to provide your parameters *once*, and after that, we ignore them.

Will you be using this functional singleton pattern in your code? Do you have a different approach? Let me know what you think in the comments!

>Photo by [Michael Dziedzic](https://unsplash.com/@lazycreekimages?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash
