Dustin Coates in Alexa

EmitWithState: the final piece of alexa.js for the Node.JS Alexa Skills Kit SDK

In this post we’re going to look at the final parts of alexa.js, focusing on EmitWithState, but also looking at how exceptions are caught if they aren’t elsewhere, how state handlers are created, and what is exported.

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…

function createStateHandler(state, obj){
  if(!obj) {
    obj = {};
  }

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

  return obj;
}

function EmitWithState() {
  if(arguments.length === 0) {
    throw new Error('EmitWithState called without arguments');
  }
  arguments[0] = arguments[0] + this.state;

  if (this.listenerCount(arguments[0]) < 1) {
    arguments[0] = 'Unhandled' + this.state;
  }

  if (this.listenerCount(arguments[0]) < 1) {
    throw new Error(`No 'Unhandled' function defined for event: ${arguments[0]}`);
  }

  this.emit.apply(this, arguments);
}

process.on('uncaughtException', function(err) {
  console.log(`Uncaught exception: ${err}\n${err.stack}`);
  throw err;
});

module.exports.LambdaHandler = alexaRequestHandler;
module.exports.CreateStateHandler = createStateHandler;
module.exports.StateString = _StateString;

createStateHandler

function createStateHandler(state, obj){
  if(!obj) {
    obj = {};
  }

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

  return obj;
}

The first thing we’ll look at is the createStateHandler function. The name is a bit misleading, as it doesn’t in fact create a handler. It is, however, exported as CreateStateHandler and we use it to attach a state to our handlers.

What it does is it takes an object (which should contain our handlers for a given state) and defines a property on it. That property is the _StateString ('STATE'), with a value of either the provided state or an empty string. It’s important to note that it is using defineProperty and not just adding a new key/value pair. This is as new properties are not enumerable in a for...in loop. Because of this, RegisterHandlers does not need to check for and skip anything with a key of STATE.

For what it’s worth, this code feels overly cautious. I’m hard-pressed to think of a situation where you would call this function without providing an object (so no need to assign obj to an empty one) and even harder pressed to think of a situation where you would call it without passing a state value in. I could be wrong, so tweet at me if that’s the case.

EmitWithState

function EmitWithState() {
  if(arguments.length === 0) {
    throw new Error('EmitWithState called without arguments');
  }
  arguments[0] = arguments[0] + this.state;

  if (this.listenerCount(arguments[0]) < 1) {
    arguments[0] = 'Unhandled' + this.state;
  }

  if (this.listenerCount(arguments[0]) < 1) {
    throw new Error(`No 'Unhandled' function defined for event: ${arguments[0]}`);
  }

  this.emit.apply(this, arguments);
}

Unlike our previous, EmitWithState does exaclty what it says it will: emit with the state. This function does what I would expect: throw if there are no arguments provided.

EmitWithState was bound to our handler object, so when we see after the conditional that we are getting the state from the current context, that’s what is happening. In other words, we’ve set the state inside our code. This code is taking the function (this can be, but doesn’t have to be an intent) and prepending it to the current state. For example, we could wish to trigger the getScore function with a mid-game state (let’s say it’s represented by the string _MIDGAME). In this case, what will be emitted will be getScore_MIDGAME.

In the next conditional, it’s seeing if there are any registered listeners with the newly-formed string. If not, then this is clearly an unhandled intent (no handlers === unhandled). So instead of looking for that intent, we want to see if there’s an unhandled handler on the current state. If there’s none of that, then an error is thrown.

Finally, if it gets through all of that, the event string (either the intent plus state or `Unhandled` plus state) is emitted with all of the arguments, allowing us to pass arguments to—again—either the intent handlers or any other function.

process.on('uncaughtException', function(err) {
  console.log(`Uncaught exception: ${err}\n${err.stack}`);
  throw err;
});

This is simple: if there’s an uncaught exception, we log out the stack trace and throw the error. Useful for logging (e.g. in CloudWatch).

module.exports.LambdaHandler = alexaRequestHandler;
module.exports.CreateStateHandler = createStateHandler;
module.exports.StateString = _StateString;

Finally, the exportation of three things:
- alexaRequestHandler (as LambdaHandler), which is renamed just handler by the time it’s available to us
- createStatehandler (as CreateStateHandle), which is available to us as CreateStateHandler
- _StateString as StateString, which is also available to us with that name

That’s it for alexa.js! It’s been a long part of the series, next up we’ll look at response.js.