Using ES2016 Decorators to Publish on an Event Bus

postal.js Sep 25, 2015

One stage 1 feature for ES2016 is the idea of Decorators proposed by Yehuda Katz. They allow you to annotate and modify entire classes, methods, and getters/setters.

We can use ES2016 decorators to attach behavior to methods on a class. With this we can do things like publishing to an event bus when a method is invoked.

First of all, a decorator is nothing more than a function. This function can optionally take arguments as options and return another function that does the decorating. The decorator function will recieve as arguments the target (which is the constructor the method exists on), the name of the function, and the descriptor of the function.

import postal from "postal/lib/postal.lodash";

export default function publish(topic, channel) {
  return function(target, name, descriptor) {
    const fn = descriptor.value;

    descriptor.value = function() {
      let value = fn.apply(this, arguments);
      postal.channel(channel || target.channel || "/").publish(topic, value);
    };
  };
}

Here we're allowing 2 arguments on the decorator itself, the topic and the channel. Then the decorator function is returned.

Inside the decorator function we can create a copy of the original method and make it constant. Then we can override the original function and call the original one that was saved. This allows us to recieve the returned value of the function.

You can then publish out on your event bus the returned value on the specified topic and channel.

Postal.js is the event bus in these examples which was written by an amazingly talented JavaScript Engineer Jim Cowart

And here is how you use the decorator.

import publish from "path/to/decorators/publish";

class FooComponent () {
  @publish("foo.some.message", "component")
  someMethod() {
    return {
      my: "data"
    };
  }
  @publish("foo.some.other")
  anotherMethod() {
    // ...
  }
}

So now, when you call someMethod or anotherMethod it will publish an event...

let foo = new FooComponent();

foo.someMethod(); //  publish "foo.some.message" on "component" channel with { my: "data" }
foo.anotherMethod(); //  publish "foo.some.other" on "/" channel with no data

Tags