Dustin Coates in Alexa

RegisterHandlers: Yes, we're listening on the Alexa Skills Kit SDK for Node.js

RegisterHandlers is the function that takes all of our handlers and sets up event listeners for them. In this post, we’ll examine how it does that. As a reminder, this is the Dig Deep series, where we look line-by-line at the tools and libraries we use to build voice-first experiences. This is not the place to go for tutorials, but if you want to learn interesting little nuggets about what you use every day, off we go…

RegisterHandlers is used in two places in this code. First, it’s used inside the alexaRequestHandler to define a method called registerHandlers on our handler object. This means that we have access to registerHandlers in our code as well, which you know well as it’s required for the setup of any skill use the JavaScript SDK. This method does nothing more than calls RegisterHandlers with the handler object set as the value of this.

The second place it’s used is also in alexaRequestHandler, to register the responseHandlers. We’ll go over these in-depth in a future post, but for now know of them as the :tell, :ask, etc. events.

Here’s the code in full. Warning, it’s a long one.

function RegisterHandlers() {
  for(var arg = 0; arg < arguments.length; arg++) {
    var handlerObject = arguments[arg];

    if(!isObject(handlerObject)) {
      throw new Error(`Argument #${arg} was not an Object`);
    }

    var eventNames = Object.keys(handlerObject);

    for(var i = 0; i < eventNames.length; i++) {
      if(typeof(handlerObject[eventNames[i]]) !== 'function') {
        throw new Error(`Event handler for '${eventNames[i]}' was not a function`);
      }

      var eventName = eventNames[i];

      if(handlerObject[_StateString]) {
        eventName += handlerObject[_StateString];
      }

      var localize = function() {
        return this.i18n.t.apply(this.i18n, arguments);
      };

      var handlerContext = {
        on: this.on.bind(this),
        emit: this.emit.bind(this),
        emitWithState: EmitWithState.bind(this),
        state: this.state,
        handler: this,
        i18n: this.i18n,
        locale: this.locale,
        t : localize,
        event: this._event,
        attributes: this._event.session.attributes,
        context: this._context,
        callback : this._callback,
        name: eventName,
        isOverridden:  IsOverridden.bind(this, eventName),
        // response: ResponseBuilder(this)
        // Note: as of 1.0.12, ResponseBuilder is a JavaScript class
        response: new ResponseBuilder(this)
      };

      this.on(eventName, handlerObject[eventNames[i]].bind(handlerContext));
    }
  }
}
for(var arg = 0; arg < arguments.length; arg++) {
  var handlerObject = arguments[arg];

  if(!isObject(handlerObject)) {
    throw new Error(`Argument #${arg} was not an Object`);
  }

  // Other code
}

First off, the code is looping through the arguments object. This allows for a non-fixed number of handler objects, useful for when we use CreateStateHandler to create them. Then it’s setting it to handlerObject (useful to know so we don’t look at future code and say: “Wait, where’d this come from?”) and making sure it’s an object.

var eventNames = Object.keys(handlerObject);

for(var i = 0; i < eventNames.length; i++) {
  if(typeof(handlerObject[eventNames[i]]) !== 'function') {
    throw new Error(`Event handler for '${eventNames[i]}' was not a function`);
  }

  var eventName = eventNames[i];

  if(handlerObject[_StateString]) {
    eventName += handlerObject[_StateString];
  }

  // Clipped for space
}

Next up we see that we’re grabbing the event names from the keys of the handlerObject, because recall that when we create our objects, it’s something like {'AMAZON.YesIntent': function(){}}.

Then, the code is looping over them one-by-one and checking to see if each is a function. Fair enough—our handlers wouldn’t work otherwise.

Finally, we’re grabbing the event name from the current iteration of the loop and concatenating it with the state that’s defined on the object if that’s present.

Wait, what? We never defined anything resembling {STATE: 'GUESSMODE'}, right? Well, not directly, but we may have used CreateStateHandler, which did. We haven’t looked at that portion of the code yet, so spoiler alert:

Object.defineProperty(obj, _StateString, {
  value: state || ''
});

This comes from CreateStateHandler and defines a new property with a value of STATE on our handler objects. By using defineProperty, it doesn’t come up when looking at all of the keys and it doesn’t need to be skipped over in the loop above.

var localize = function() {
  return this.i18n.t.apply(this.i18n, arguments);
};

Here the code is simply creating a new function (localize) that sends the current arguments to the i18n.t method with i18n as the scope. (Recall that i18n could be overwritten, but is probably always the package i18next.) Next up we’ll see that we’ll call localize by a different name.

var handlerContext = {
  on: this.on.bind(this),
  emit: this.emit.bind(this),
  emitWithState: EmitWithState.bind(this),
  state: this.state,
  handler: this,
  i18n: this.i18n,
  locale: this.locale,
  t : localize,
  event: this._event,
  attributes: this._event.session.attributes,
  context: this._context,
  callback : this._callback,
  name: eventName,
  isOverridden:  IsOverridden.bind(this, eventName),
  response: ResponseBuilder(this)
};

this.on(eventName, handlerObject[eventNames[i]].bind(handlerContext));

In this snippet, the context is being set for our handler. There’s fiften different properties to look at, so we’ll do that one-by-one:

on, emit, emitWithState

Alright, I knew I said we’d do this one-by-one, but I don’t want to repeat myself three times and you don’t want me to repeat myself three times. For the on and emit methods, the code is binding the current context to these methods. Such is the way of JavaScript: if this wasn’t done, when we called either of these methods, this would be something else and we could no longer find on or emit and, well, it’s just what we’ve gotta do.

emitWithState is the odd one out here as it’s not binding this to a method currently on this but to EmitWithState. EmitWithState is a function that will call any registered event with the state concatenated onto the end. Showing us once again that if you name your functions well, they’re fairly self-documenting.

handler

The value of handler is simply this, which is the handler object that was set up all the way back in alexaRequestHandler. The upshot here is that we have access to everything on the handler. Indeed, most of what’s happening here is Amazon providing us a gentler way of getting at commonly used resources. Very kind of them.

i18n, locale

Simple: the SDK is providing us with a simpler way of accessing these two. i18n is, as we looked at earlier, the internationalization module. locale comes in from the request and can currently be either British English, American English, or German.

t

We just saw the function localize. This is nothing more than that function, likely named t to keep consistent with most internationalization libraries, who has this method name as a convention for the translate functionality.

event, attributes, context, callback, name

Simply _event, _context, and _callback, respectively. We of course still have access to these “private” objects through handler but there’s no need to use them. attributes reaches deep into event, err… well, _event. name is eventName, which by this point has been concatenated with the specified state.

isOverridden

This is a short method, so I’ll just reproduce it here:

function IsOverridden(name) {
  return this.listenerCount(name) > 1;
}

This checks to see if there are multiple listeners (listenerCount comes from EventEmitter). This should really only be used inside the response handlers (we’ll see later). If your own handlers are overridden, then you’ve got cruft code that you should remove.

response

This is calling ResponseBuilder, passing it in this. This function’s a doozy, so we’ll look at ResponseBuilder in its own post.