Pub-Sub using ES6 Promise
Tags: javascript, programming
Published: 2023-10-30 05:10:18

Impossible !!

A Promise just resolve one value but not a stream of values. 

Telling lies :raised_eyebrow:?? no papa 

Out of curiosity I was exploring the possibility of a Publish-Subscribe model using Promises. The result is very interesting and it really brushed up my knowledge on call by value and call by reference too.

So here's the idea. create a function that return a tuple containing an object to `subscribe`(this returns a function to unsubscribe), `reset` and a `resolver` function to emit values to subscribers. 

```javascript
function promisedPubSub() {
  let callbackRegistery = Object.create(null);

  const subscriptions = {
    subscribe: (callback) => {
       const token = `uid_${Math.random().toString(36).substring(2)}`;
      callbackRegistery[token] = fn;
      return () => {
        delete callbackRegistery[token];
      };
    },
    reset: () => {
       callbackRegistery = Object.create(null);
    }
  }
  
  return [subscriptions, () => {}]
}

// usage
const [subscriptions, resolver] = promisedPubSub();

subscriptions.subscribe((val) => { console.log('a', val); })
subscriptions.subscribe((val) => { console.log('b', val); })

resolver(1);
```

You can see that we can add subscribers using `subscriptions.subscribe` but when we call `resolver`, it is doing nothing as it is a blank function.

So let's modify the above implementation:

```javascript
function promisedPubSub() {
  let instance, resolver, callbackRegistery = Object.create(null);

  const subscriptions = {
    subscribe: (fn) => {
      const token = `uid_${Math.random().toString(36).substring(2)}`;
      callbackRegistery[token] = fn;
      return () => {
        delete callbackRegistery[token];
      };
    },
    reset: () => {
       callbacks = [];
    }
  }

  function initiate() {
    instance = null;
    resolver = null;
    instance = new Promise((resolve) => {
       resolver = resolve;
    });

    instance.then((val) => {
       const callbacks = Object.values(callbackRegistery);
       callbacks.forEach((callback) => {
         callback(val);
       });
    })
  }

  initiate();
  
  return [subscriptions, (val) => { resolver(val); }]
}
```

Yea that's a mouthful. Let me explain what we're doing here.

We created an internal function `initiate` that create a new Promise. As Promise constructor invokes immediately, the resolver variable got the resolve function reference.

Now when we execute the usage code, it logs `a 1 b 1` means, when we emit 1 all the subscribers received that value. but if you try to emit another value nothing happen.

You already know the answer for why nothing happen. 

Promise only resolve once. 

So how to emit multiple values?

Answer: Recursion.

Remember we wrote an internal function `initate`? we need to call the same function when the promise is resolved.

So this is how the final code looks like:

```javascript
const promisedPubSub = () => {
  let instance,
    resolver,
    callbackRegistery = Object.create(null);

  const subscriptions = {
    subscribe: (fn) => {
      const token = `uid_${Math.random().toString(36).substring(2)}`;
      callbackRegistery[token] = fn;
      return () => {
        delete callbackRegistery[token];
      };
    },
    reset: () => {
      instance = null;
      resolver = null;
      callbackRegistery = Object.create(null);
    },
  };

  function initiate() {
    instance = null;
    resolver = null;
    instance = new Promise((resolve) => {
      resolver = resolve;
    });
    instance.then((val) => {
      const callbacks = Object.values(callbackRegistery);
      callbacks.forEach((callback) => {
        callback(val);
      });
      setTimeout(() => {
        initiate();
      });
    });
  }

  initiate();

  return [
    subscriptions,
    (val) => {
      if(resolver) {
        resolver(val);
      }
    },
  ];
};
```

And this is the working example:

{% stackblitz https://stackblitz.com/edit/js-jxmbcv?ctl=1&devToolsHeight=33&embed=1&file=index.js %}

Congratulations we made the impossible a possible :star_struck:

And that's it. 

See you in next article. 

Thanks,
Kiran :wave: :wave: