Code Review: Superagent

08 Aug 2011 | By Alex Young | Tags code-review

Code Review is a series on DailyJS where I take a look at an open source project to see how it’s built. Along the way we’ll learn patterns and techniques by JavaScript masters. If you’re looking for tips to write better apps, or just want to see how they’re structured in established projects, then this is the tutorial series for you.

About Superagent

Superagent (GitHub: visionmedia / superagent, License: MIT, npm: superagent) by TJ Holowaychuk is a small HTTP request library. My interest in the library was originally piqued when TJ criticised the jQuery Ajax API, apparently building Superagent out of sheer frustration.

jQuery’s API while having recently added some promise-like support, is largely static, forcing you to build up big objects containing all the header fields and options, not to mention most of the options are awkwardly named “type” instead of “method”, etc.

A common technique for mitigating high arity or complex Object arguments is to use a chained API, which is exactly what TJ has done:

request
  .post('/api/pet')
  .data({ name: 'Manny', species: 'cat' })
  .set('X-API-Key', 'foobar')
  .set('Accept', 'application/json')
  .end(function(res){
    if (res.ok) {
      alert('yay got ' + JSON.stringify(res.body));
    } else {
      alert('Oh no! error ' + res.text);
    }
  });

Another issue TJ cites is with callback signatures. jQuery’s Ajax callbacks take three parameters: data, textStatus, and xhr, but TJ has replaced all of these with one object.

As the man behind Express, it’s not surprising that TJ has an extremely solid grasp of HTTP-based APIs. He’s also corrected other inconsistent behaviour, like 4/500 response handling.

This library has nascent Node support. I’ve tested it out and it’s already looking like a clean and simple way to fire off server-side HTTP requests.

Structure

This project is fairly typical for TJ (and a growing number of Node-based developers); it includes a Makefile, package.json with developer dependencies for tests, and source under lib/. The Makefile includes recipes for building and minimising the client-side code, which I suggest you reuse if you’re interested in building client-side projects with JavaScript across the entire stack.

TJ includes a very neat little EventEmitter library for browsers to use. Since this project isn’t built specifically for jQuery there’s other chunks of functionality that would typically be found in a library: in the main source file there’s isFunction and isObject.

Superagent also has to deal with Microsoft’s XMLHttpRequest implementation:

var getXHR = 'XMLHttpRequest' in this
  ? function(){ return new XMLHttpRequest }
  : function(){ return new ActiveXObject('Microsoft.XMLHTTP') };

I know that to support a wide range of Microsoft’s implementations this might need to be extended with Msxml2.XMLHTTP.6.0, Msxml2.XMLHTTP.3.0, and Msxml2.XMLHTTP. This is partially documented in Microsoft’s XMLHttpRequest Object documentation (but doesn’t cover IE prior to 7).

While we’re on the subject of IE, the tests depend on a native version of the JSON object which IE doesn’t have. Most projects usually include json2 to accomplish this. I expect TJ just hasn’t had time to explore IE fully yet (he only released Superagent yesterday).

The default MIME types should be easy to extend through the types property:

exports.types = {
      html: 'text/html'
    , json: 'application/json'
    , urlencoded: 'application/x-www-form-urlencoded'
  };

The MIME types are then backed up with associated parsers:

exports.parse = {
      'application/x-www-form-urlencoded': parseString
    , 'application/json': JSON.parse
  };

The whole library is built around the Request and Response objects, which is extremely reminiscent of Express. They both wrap functionality around the XMLHttpRequest object, and are well documented and easy to follow. The Request object inherits from EventEmitter, and calls the original constructor:

function Request(method, url) {
  var self = this;
  EventEmitter.call(this);

// ...

Request.prototype = new EventEmitter;
Request.prototype.constructor = Request;

Using EventEmitter this way makes dealing with asynchronous requests trivial:

this.on('end', function(){
  self.callback(new Response(self.xhr));
});

// state change
xhr.onreadystatechange = function(){
  if (4 == xhr.readyState) self.emit('end');
};

Conclusion

Superagent’s API is definitely nice, and the project has been packaged in a careful and friendly way. Although I suspect a few browser quirks might need patching to encourage wide adoption, it should be fairly trivial for people to contribute such patches — considering the documentation and tests that TJ has already written.


blog comments powered by Disqus