Pronto.js: Creating and chaining commands

Sep 7 2012

Pronto.js is a JavaScript framework for Node.js designed for writing fast, efficient, asynchronous, component-based applications. It can be used for web applications, REST API servers, command-line programs, and so on.

Pronto.js is based on the idea that code consists of three major conceptual pieces. First there is a route. A route executes a chain of commands. So writing a Pronto application is all about writing commands, and then organizing them into chains mapped to routes.

Here we look at how to write commands. In this article, we will build a small sample app composed of two commands chained together in a single route. We'll highlight how to write commands and how to share components through the context. <!--break-->

Creating a First Command: RandomName

The first command is our RandomName command. It's job is to put a random name into the context so that other commands can later use that name. We're not going to be all that ambitious with our first command. It'll be basic.

Conceptually, all commands in Pronto.js inherit the pronto.Command prototype. That prototype has some core event logic that allows Pronto to supervise the command's lifecycle.

When it comes to implementing a new command, there are three basic steps:

  1. Create a new command constructor.
  2. Make sure the new command inherits pronto.Command (Tip: there's a utility function that does this for you.)
  3. Declare the execute() function.

With these in mind, here is the complete code for the RandomName command:

var pronto = require('pronto');

// Constructor
function RandomName() {
  // The pool of names.
  this.names = ['Anna', 'Bob', 'Claire', 'Dennis'];
}
module.exports = RandomName;

// Inherit the Command prototype.
pronto.inheritsCommand(RandomName);

// The command body.
RandomName.prototype.execute = function (cxt, params) {
  // Get a random number within this.name's range.
  var i = Math.floor(Math.random() * this.names.length);

  // Get a random name.
  var randomName = this.names[i];

  // We're done, here's the name.
  this.done(randomName);
}

There's not much to the RandomName() constructor. It just declares our paltry pool of name possibilities. Often, command constructors are empty.

RandomName.prototype.execute is where most of the code lives. The execute() function, when run, will be given two parameters:

  • cxt: The pronto.Context object. This has information about the currently executing request. It also functions as a name/value storage for the current request. We'll return to it later.
  • params: An Object of name/value pairs passed into this command. We'll see more of this later.

For our simple example, we won't directly use either of these. But we will indirectly use the context.

The first few lines of the execute() function are pretty straightforward: We just retrieve a random name from this.names.

But the last line of execute() is the key.

  this.done(randomName);

This call does two very important things:

  1. It stores randomName in the context so that another command can use it.
  2. It signals Pronto that it is done executing.

Why is that second one necessary? won't return do that? The answer is no. Pronto is built to be asynchronous. It's possible that a command will execute a callback, but shouldn't actually notify Pronto that it is complete until the callback is fired. As a quick example, we could read a file like this:


SomeCommand.prototype.execute = function (cxt, params) {

  // Make sure we can still access this
  var cmd = this;

  fs.readFile('foo.txt', function (data) {
    cmd.done(data);
  });
}

The command above doesn't notify Pronto that it is complete until the file's contents have been read.

That's all there is to our first command. Now let's write a second one -- one that takes advantage of Pronto's ability to pass parameters to commands.

Creating a Second Command: SayHi

The second command is designed to "say hi" on the console. It takes a name, and optionally an alternate greeting.

Here's the entire code to SayHi:

var pronto = require('pronto');

// Constructor
function SayHi() {
}
module.exports = SayHi;

// Inherit the Pronto Command prototype.
pronto.inheritsCommand(SayHi);

// This is the command.
SayHi.prototype.execute = function (cxt, params) {

  // One param is required. It's the 'to' param.
  this.required(params, ['to']);

  var to = params.to;

  // If the param 'message' is specified, we will use
  // this to generate the message. Otherwise we will
  // use the default string.
  var message = params.message || 'Hi %s.';

  // Say 'hi' to someone.
  console.log(message, to);

  // Tell Pronto we're done.
  this.done();
}

This time around, the boilerplate at the top should be familiar: We require pronto, export the command, create a constructor, and inherit the pronto.Command prototype.

Let's focus on the SayHi.prototype.execute function.

The first thing this does is declare that it requires some params. In this case, it actually requires only one:

  // One param is required. It's the 'to' param.
  this.required(params, ['to']);

The base pronto.Command object will ensure that every command in the Array is present before continuing. In other words, it requires that Pronto pass it in values for those parameters. we require that a to parameter be passed in. This is the name of the person we will say "hi" to.

Not all parameters have to be required. Any parameters can be passed in on the param object.

It's best practice to clearly document what parameters a command can take, and also to assign all params a to a local variable right at the top of the execute() function so that others can clearly see this at a glance.

Our code above accepts one addition non-required parameter: message. If this is set, it will be used. Otherwise, the default message of Hi %s is used.

So we now have two parameters:

  • to: Who we are saying "Hi" to.
  • message: The string we will use to say "hi".

Time to print those to the console: console.log(message, to).

From there, we just need to tell Pronto we're done: this.done(). We don't pass anything into done() this time, so nothing is stored in the context for this object.

Putting the Commands Together

Now we can write a Pronto program to use our two new commands. We're going to write an example program that says "Hi" to a random person. Here is the code, which I have in example.js.

var pronto = require('pronto');
var RandomName = require('./lib/randomname');
var SayHi = require('./lib/sayhi');

var register = new pronto.Registry();
var router = new pronto.Router(register);

register.route('example')
  .does(RandomName, 'name')
  .does(SayHi)
    .using('to').from('cxt:name')


router.handleRequest('example');

This is a basic single-run console app for Pronto (as opposed to a web app, which would start up a server). If any of the boilerplate looks unfamiliar, you may want to check out Creating Apps with Pronto.

Here's the important part:

register.route('example')
  .does(RandomName, 'name')
  .does(SayHi)
    .using('to').from('cxt:name')

This registers a route, called example, that will do the following when run:

  1. Execute RandomName and store the name in the context as name. (Remember that call to done(randomName)? That'll be in the context with the key name.)
  2. Execute SayHi passing the parameter to the value of cxt:name. That last part (from) will cause Pronto to look in the context for a key name, which is the randomName we created earlier.

The output, then, will be something like this:

Hi Claire.

Now with a minor change, we can change the greeting:

register.route('example')
  .does(RandomName, 'name')
  .does(SayHi)
    .using('to').from('cxt:name')
    .using('message', 'Hello %s')

Now we are passing in a second parameter. And we want to set it's value directly, not having the value retrieved from (from()) somewhere else. So we just do using('message', 'Hello %s'). Now the output will be:

Hello Claire

And that is how we pass values from one command to another. (The technical name for this, by the way, is dependency injection, and this is very close to the design pattern known as Inversion of Control (IoC).)

In a well-constructed Pronto chain, each command will be responsible for one discrete operation. For a database tool, then, you might have separate commands to do each of the following:

  • Open a database connection (putting the connection in the context)
  • Run a query (using the connection, and returning the result set)
  • Iterating over the result set (using the result set, perhaps printing the value)
  • Closing the connection (using the connection)

It may at first seem tedious until you begin building bigger tools. Well-constructed components are highly re-usable, and can rapidly be re-assembled into other routes and applications.