¶ ClientPromise-based HTTP client around the request library. |
|
¶ EventsA client is an event emitter. The following events can be emitted:
See Internals for more information on these events. |
|
¶ DependenciesThe following external libraries are used:
|
var _ = require('underscore'),
events = require('events'),
q = require('q'),
util = require('util');
|
¶ ExportsThis module exports a factory function the can be used to inject mock dependencies. The following dependencies can be passed to the function in order:
For example:
|
module.exports = function(request) {
|
¶ ConstructorConstructs a new HTTP client. No options are required. |
function Client() {
this.requestNumber = 0;
events.EventEmitter.call(this);
}
util.inherits(Client, events.EventEmitter);
_.extend(Client.prototype, {
|
¶ #request(options)Starts an HTTP request and returns a promise that will be resolved with the HTTP response or rejected if an error occurs.
Look at the request library documentation for available options. Additional options:
|
request: function(options) {
options = options || {};
var startTime = new Date().getTime(),
requestNumber = ++this.requestNumber,
requestHandler = options.handler;
|
The returned promise is part of chain that starts with the asynchronous construction of the request options. |
var promise = q.fcall(_.bind(this.buildRequestOptions, this), options).then(_.bind(this.checkRequestOptions, this));
|
Then the HTTP request is started. |
promise = promise.then(_.bind(this.sendRequest, this, requestNumber, requestHandler, startTime));
|
Finally, handlers are added at the end of the promise chain to emit |
return promise.spread(_.bind(this.handleResponse, this, requestNumber, startTime), _.bind(this.handleError, this, requestNumber));
},
|
¶ Internals |
sendRequest: function(requestNumber, requestHandler, startTime, requestOptions) {
|
EVENT: the |
this.emit('request', requestNumber, requestOptions);
var deferred = q.defer();
|
The returned promise is resolved if the request succeeds, rejected if it fails. |
var r = request(requestOptions, function(err, response, body) {
if (err) {
return deferred.reject(err);
}
deferred.resolve([ response, body ]);
});
|
If specified, the request handler function is called with the request object. |
if (requestHandler) {
requestHandler(r);
}
return deferred.promise;
},
handleError: function(requestNumber, err) {
|
EVENT: the |
this.emit('error', requestNumber, err);
return q.reject(err);
},
handleResponse: function(requestNumber, startTime, response) {
|
EVENT: the |
this.emit('response', requestNumber, response, new Date().getTime() - startTime);
return response;
},
|
An error is thrown if either the |
checkRequestOptions: function(options) {
if (!_.isString(options.method)) {
throw new Error('"method" must be a string, got ' + options.method);
} else if (!_.isString(options.url)) {
throw new Error('"url" must be a string, got ' + options.url);
}
return options;
},
buildRequestOptions: function(options) {
if (!_.isObject(options)) {
throw new Error('Request options must be an object, got ' + typeof(options));
} else if (options.handler !== undefined && typeof(options.handler) != 'function') {
throw new Error('"handler" must be a function, got ' + typeof(options.handler));
}
|
Options for the request library are the same as the ones provided to |
var requestOptions = _.extend(_.omit(options, 'filters', 'handler'), {
url: options.url
});
|
The HTTP method is automatically converted to uppercase. |
if (_.isString(options.method)) {
requestOptions.method = options.method.toUpperCase();
}
var promise = q(requestOptions);
if (options.filters) {
_.each(options.filters, function(filter, i) {
|
Request filters are executed in a promise chain so that they may be asynchronous. |
promise = promise.then(filter).then(_.bind(this.ensureRequestOptions, this, i));
}, this);
}
return promise;
},
|
After each filter, this check ensures that the returned request options are valid. Again, any error is caught by the promise chain and causes the returned promise to be rejected. |
ensureRequestOptions: function(filterIndex, options) {
if (options === undefined) {
throw new Error('Request filter at index ' + filterIndex + ' returned nothing; it must return the filtered request options');
} else if (!_.isObject(options)) {
throw new Error('Expected request filter at index ' + filterIndex + ' to return the request options as an object, got ' + typeof(options));
}
return options;
}
});
return Client;
};
module.exports['@singleton'] = true;
module.exports['@require'] = [ 'request' ];
|