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 👋 👋

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay