Permalink
Browse files

add req.timeout(ms) support for the client

  • Loading branch information...
1 parent 75776e1 commit 90335f57686d90b9baaef493713371f362bcef41 @tj tj committed Nov 15, 2012
Showing with 139 additions and 18 deletions.
  1. +58 −4 build/build.js
  2. +58 −4 lib/client.js
  3. +2 −9 lib/node/index.js
  4. +8 −1 test/server.js
  5. +13 −0 test/test.request.js
View
@@ -733,7 +733,7 @@ function Request(method, url) {
this.header = {};
this.set('X-Requested-With', 'XMLHttpRequest');
this.on('end', function(){
- self.callback(new Response(self.xhr));
+ self.callback(null, new Response(self.xhr));
});
}
@@ -745,7 +745,33 @@ Request.prototype = new Emitter;
Request.prototype.constructor = Request;
/**
- * Abort the request.
+ * Set timeout to `ms`.
+ *
+ * @param {Number} ms
+ * @return {Request} for chaining
+ * @api public
+ */
+
+Request.prototype.timeout = function(ms){
+ this._timeout = ms;
+ return this;
+};
+
+/**
+ * Clear previous timeout.
+ *
+ * @return {Request} for chaining
+ * @api public
+ */
+
+Request.prototype.clearTimeout = function(){
+ this._timeout = 0;
+ clearTimeout(this._timer);
+ return this;
+};
+
+/**
+ * Abort the request, and clear potential timeout.
*
* @return {Request}
* @api public
@@ -756,6 +782,7 @@ Request.prototype.abort = function(){
this.xhr.abort();
this.emit('abort');
this.aborted = true;
+ this.clearTimeout();
return this;
};
@@ -917,6 +944,22 @@ Request.prototype.send = function(data){
};
/**
+ * Invoke the callback with `err` and `res`
+ * and handle arity check.
+ *
+ * @param {Error} err
+ * @param {Response} res
+ * @api private
+ */
+
+Request.prototype.callback = function(err, res){
+ var fn = this._callback;
+ if (2 == fn.length) return fn(err, res);
+ if (err) return this.emit('error', err);
+ fn(res);
+};
+
+/**
* Initiate request, invoking callback `fn(res)`
* with an instanceof `Response`.
*
@@ -929,16 +972,27 @@ Request.prototype.end = function(fn){
var self = this;
var xhr = this.xhr = getXHR();
var query = this._query.join('&');
+ var timeout = this._timeout;
var data = this._data;
// store callback
- this.callback = fn || noop;
+ this._callback = fn || noop;
// state change
xhr.onreadystatechange = function(){
- if (4 == xhr.readyState) self.emit('end');
+ if (4 == xhr.readyState && 0 != xhr.status) self.emit('end');
};
+ // timeout
+ if (timeout && !this._timer) {
+ this._timer = setTimeout(function(){
+ var err = new Error('timeout of ' + timeout + 'ms exceeded');
+ err.timeout = timeout;
+ self.callback(err);
+ self.abort();
+ }, timeout);
+ }
+
// querystring
if (query) {
query = request.serializeObject(query);
View
@@ -395,7 +395,7 @@ function Request(method, url) {
this.header = {};
this.set('X-Requested-With', 'XMLHttpRequest');
this.on('end', function(){
- self.callback(new Response(self.xhr));
+ self.callback(null, new Response(self.xhr));
});
}
@@ -407,7 +407,33 @@ Request.prototype = new Emitter;
Request.prototype.constructor = Request;
/**
- * Abort the request.
+ * Set timeout to `ms`.
+ *
+ * @param {Number} ms
+ * @return {Request} for chaining
+ * @api public
+ */
+
+Request.prototype.timeout = function(ms){
+ this._timeout = ms;
+ return this;
+};
+
+/**
+ * Clear previous timeout.
+ *
+ * @return {Request} for chaining
+ * @api public
+ */
+
+Request.prototype.clearTimeout = function(){
+ this._timeout = 0;
+ clearTimeout(this._timer);
+ return this;
+};
+
+/**
+ * Abort the request, and clear potential timeout.
*
* @return {Request}
* @api public
@@ -418,6 +444,7 @@ Request.prototype.abort = function(){
this.xhr.abort();
this.emit('abort');
this.aborted = true;
+ this.clearTimeout();
return this;
};
@@ -579,6 +606,22 @@ Request.prototype.send = function(data){
};
/**
+ * Invoke the callback with `err` and `res`
+ * and handle arity check.
+ *
+ * @param {Error} err
+ * @param {Response} res
+ * @api private
+ */
+
+Request.prototype.callback = function(err, res){
+ var fn = this._callback;
+ if (2 == fn.length) return fn(err, res);
+ if (err) return this.emit('error', err);
+ fn(res);
+};
+
+/**
* Initiate request, invoking callback `fn(res)`
* with an instanceof `Response`.
*
@@ -591,16 +634,27 @@ Request.prototype.end = function(fn){
var self = this;
var xhr = this.xhr = getXHR();
var query = this._query.join('&');
+ var timeout = this._timeout;
var data = this._data;
// store callback
- this.callback = fn || noop;
+ this._callback = fn || noop;
// state change
xhr.onreadystatechange = function(){
- if (4 == xhr.readyState) self.emit('end');
+ if (4 == xhr.readyState && 0 != xhr.status) self.emit('end');
};
+ // timeout
+ if (timeout && !this._timer) {
+ this._timer = setTimeout(function(){
+ var err = new Error('timeout of ' + timeout + 'ms exceeded');
+ err.timeout = timeout;
+ self.callback(err);
+ self.abort();
+ }, timeout);
+ }
+
// querystring
if (query) {
query = request.serializeObject(query);
View
@@ -569,14 +569,8 @@ Request.prototype.request = function(){
Request.prototype.callback = function(err, res){
var fn = this._callback;
-
- // flag as finished so that
- // timeouts may be ignored
- this._finished = true;
-
- // invoke callback
+ this.clearTimeout();
if (2 == fn.length) return fn(err, res);
-
if (err) return this.emit('error', err);
fn(res);
};
@@ -604,12 +598,11 @@ Request.prototype.end = function(fn){
// timeout
if (timeout && !this._timer) {
this._timer = setTimeout(function(){
- if (self._finished) return;
var err = new Error('timeout of ' + timeout + 'ms exceeded');
err.timeout = timeout;
- self.callback(err);
self._aborted = true;
req.abort();
+ self.callback(err);
}, timeout);
}
View
@@ -41,6 +41,13 @@ app.get('/no-content', function(req, res){
res.send(204);
});
+app.get('/delay/:ms', function(req, res){
+ var ms = ~~req.params.ms;
+ setTimeout(function(){
+ res.send(200);
+ }, ms);
+});
+
app.put('/user/:id', function(req, res){
res.send('updated');
});
@@ -93,5 +100,5 @@ app.get('/foo', function(req, res){
app.use(express.static(__dirname + '/../'));
-app.listen(3001);
+app.listen(3000);
console.log('Test server listening on port 3000');
View
@@ -457,3 +457,16 @@ test('request(url, fn)', function(next){
next();
});
});
+
+test('req.timeout(ms)', function(next){
+ request
+ .get('/delay/3000')
+ .timeout(1000)
+ .end(function(err, res){
+ assert(err, 'error missing');
+ assert(1000 == err.timeout, 'err.timeout missing');
+ assert('timeout of 1000ms exceeded' == err.message, 'err.message incorrect');
+ assert(null == res);
+ next();
+ })
+})

0 comments on commit 90335f5

Please sign in to comment.