DEV Community

Kiran Mantha
Kiran Mantha

Posted on

Pub-Sub using ES6 Promise

Impossible !!

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

Telling lies ๐Ÿคจ?? 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.

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);
Enter fullscreen mode Exit fullscreen mode

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:

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); }]
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
      }
    },
  ];
};
Enter fullscreen mode Exit fullscreen mode

And this is the working example:

Congratulations we made the impossible a possible ๐Ÿคฉ

And that's it.

See you in next article.

Thanks,
Kiran ๐Ÿ‘‹ ๐Ÿ‘‹

Top comments (0)