A javascript triple phase event system

Marco Asbreuk
Frontend Weekly
Published in
3 min readOct 15, 2017

It already seems a long time ago when my favorite framework YUI3 ended lifetime. YUI3 was ahead of time in many ways. Some of its features still seem to be re-invented nowadays.

My favorite part was YUI’s Custom Events. It has a triple phase event system, working throughout the whole framework.

This was the inspiration to create itsa-event: a javascript triple phase event system. No framework, just a plain module that brings in those powerful features. It also supports asynchronicity, through e.returnValue that can be a Promise with a future value.

You can download itsa-event on npm.

Introduction

Event systems can be:

  • Single phase (after-phase events like nodejs uses)
  • Double phased (before-phase and after-phase)
  • Triple phased (before-, action- and after-phase)

Itsa-event is a triple phase event system. When events are emitted, they go through 3 phases: before, action and an after phase.

Before phase

When an event is emitted, all before subscribers will be called in the order in which they subscribed.

The before-listeners could be used to either:

  • Call e.preventDefault()
  • Call e.halt()
  • Decorate the payload (event object).

Since any before-subscriber can interrupt the event, before subscribers should not be used get notified of an event. Instead, use it to prevent an event from happening or add information.

Default action

If no before listener interrupted the event, the default action (defaultFn) gets invoked - if defined. The default action can be defined with .defaultFn(): see the examples below.

If the defaultFn returns a value, this will be made available by e.returnValue. This value can be a Promise. The event cycle does not wait for Promises to resolve and stays high performant. The Promise return value can be inspected in the after-listeners by its thenable.

Prevented action

If any before-listener has interrupted the processing by calling e.preventDefault() on the event object, the preventedFn()-method will be called instead. Examples below show how to setup a preventedFn.

After phase

Once the event has taken place, the after listeners will be called. e.halt() and e.preventDefault() are no longer being applicable. after subscribers could change the event object but that is not recommended. All after listeners should see the same event object.

Defining Custom Events

By default you first define your custom events. You do not necessarily need to, but without, they don’t have a default function, which is typically what an event is all about: doing something when emitted.

A Custom Event can be set up like this:

The chained functions are optional, but it makes sense to define at least a defaultFn.

emitterName:eventName

Events are identified by their customEvent name, which has the syntax: emitterName:eventName.

The emitterName is usually the entity that emits the event and will be available at the eventobject's property: e.emitter. The eventName specifies the event of the emitter. It is the second part of the customEvent, after the colon. The eventName is event object's property: e.type.

Subscribing to events

Subscribing (listening) to events can be done by either the before or after listeners:

Wildcard, emitters and events

Because of the emitterName:eventName structure, you can subscribe to events in different ways:

  • emitter:event -> listen very specific
  • emitter:* -> listen to any event of a specific emitter
  • *:event -> listen to a specific event, regardless of its emitter

Emitting events

Events can be emitted with Event.emit(customEvent, payload):

Interrupting Events

Custom Events can be interrupted by invoking e.preventDefault() or e.halt() inside a before subscriber.

e.halt() will stop everything: no other before subscribers, defaultFn, preventedFn or after subscribers will be invoked.

e.preventDefault() will invoke all other before-, but no after subscribers. Also, the preventedFn will be invoked instead of defaultFn, assuming it is defined.

--

--