diff --git a/request/request.js b/request/request.js index e98cd9d1d..01b640931 100644 --- a/request/request.js +++ b/request/request.js @@ -155,6 +155,13 @@ module.exports = function($window, Promise, oncompletion) { } } + xhr.ontimeout = function (ev) { + if (aborted) return; + var error = new Error("Request timeout"); + error.timeout = ev.target.timeout; + reject(error); + } + if (typeof args.config === "function") { xhr = args.config(xhr, args, url) || xhr diff --git a/request/tests/test-request.js b/request/tests/test-request.js index 706c8404b..26e7bb6ff 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -18,7 +18,9 @@ o.spec("request", function() { o("works via GET", function(done) { mock.$defineRoutes({ "GET /item": function() { - return {status: 200, responseText: JSON.stringify({a: 1})} + return new Promise(function (resolve) { + resolve({status: 200, responseText: JSON.stringify({a: 1})}) + }) } }) request({method: "GET", url: "/item"}).then(function(data) { @@ -694,7 +696,9 @@ o.spec("request", function() { o("rejects on error in extract", function(done) { mock.$defineRoutes({ "GET /item": function() { - return {status: 200, responseText: JSON.stringify({a: 1})} + return new Promise(function (resolve) { + resolve({status: 200, responseText: JSON.stringify({a: 1})}) + }) } }) request({ @@ -707,6 +711,30 @@ o.spec("request", function() { done() }) }) + o("rejects on timeout", function(done) { + var timeout = 50 + var gotTimeoutError = false + mock.$defineRoutes({ + "GET /item": function() { + return new Promise(function (resolve) { + setTimeout(function () { + resolve({status: 200, responseText: JSON.stringify({a: 1})}) + }, timeout*2) + }) + } + }) + request( + {method: "GET", url: "/item", timeout: timeout} + ).catch(function(e) { + gotTimeoutError = true + o(e instanceof Error).equals(true) + o(e.message).equals("Request timeout") + o(e.timeout).equals(timeout) + }).then(function () { + o(gotTimeoutError).equals(true) + done() + }) + }) }) o.spec("json header", function() { function checkUnset(method) { diff --git a/test-utils/xhrMock.js b/test-utils/xhrMock.js index 55c74606f..eeaf472c2 100644 --- a/test-utils/xhrMock.js +++ b/test-utils/xhrMock.js @@ -46,6 +46,7 @@ module.exports = function() { } this.responseType = "" this.response = null + this.timeout = 0 Object.defineProperty(this, "responseText", {get: function() { if (this.responseType === "" || this.responseType === "text") { return this.response @@ -55,25 +56,49 @@ module.exports = function() { }}) this.send = function(body) { var self = this - if(!aborted) { - var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) - var data = handler({rawUrl: args.rawUrl, url: args.pathname, query: args.search || {}, body: body || null}) - self.status = data.status - // Match spec - if (self.responseType === "json") { - try { self.response = JSON.parse(data.responseText) } - catch (e) { /* ignore */ } + + var completeResponse = function (data) { + self._responseCompleted = true + if(!aborted) { + self.status = data.status + // Match spec + if (self.responseType === "json") { + try { self.response = JSON.parse(data.responseText) } + catch (e) { /* ignore */ } + } else { + self.response = data.responseText + } } else { - self.response = data.responseText + self.status = 0 + } + self.readyState = 4 + if (args.async === true) { + callAsync(function() { + if (typeof self.onreadystatechange === "function") self.onreadystatechange({target: self}) + }) } - } else { - self.status = 0 } - self.readyState = 4 - if (args.async === true) { - callAsync(function() { - if (typeof self.onreadystatechange === "function") self.onreadystatechange({target: self}) - }) + + var data + if (!aborted) { + var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) + data = handler({rawUrl: args.rawUrl, url: args.pathname, query: args.search || {}, body: body || null}) + } + + if (typeof self.timeout === "number" && self.timeout > 0) { + setTimeout(function () { + if (self._responseCompleted) { + return + } + + if (typeof self.ontimeout === "function") self.ontimeout({target: self}) + }, self.timeout) + } + + if (data instanceof Promise) { + data.then(completeResponse) + } else { + completeResponse(data) } } this.abort = function() {