DailyJS

Code Review: Superagent

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

code-review

Code Review: Superagent

Posted by Alex R. Young on .
Featured

code-review

Code Review: Superagent

Posted by Alex R. Young on .
*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.