Http class now rejects 4xx and 5xx responses

This patch makes sure that successful HTTP requests, which result
in an error code, are passed to the catch() or reject handler, rather
than the success handler.

This results in two different cases in the claim() handler, that of
an internally thrown exception (like a TypeError), and that of a
Response() instance. In most cases, developers should not write
code that throws exceptions - and even in those, the ES6 Promise
API silently swallows most uncaught throws.

Change-Id: I736bcaccf6324a3d66191692e5f2ce922a727dea
This commit is contained in:
Michael Krotscheck 2016-08-18 12:51:27 -07:00
parent abd0508c30
commit 528bccbf81
2 changed files with 43 additions and 30 deletions

View File

@ -88,6 +88,7 @@ export default class Http {
* @returns {Promise} A promise which will resolve with the processed request response.
*/
httpRequest (method, url, headers = {}, body) {
// Sanitize the headers...
headers = Object.assign({}, headers, this.defaultHeaders);
@ -100,34 +101,50 @@ export default class Http {
}
const request = new Request(url, init);
let promise = Promise.resolve(request);
// Build the wrapper promise.
return new Promise((resolve, reject) => {
// Loop through the request interceptors, constructing a promise chain.
for (let interceptor of this.requestInterceptors) {
promise = promise.then(interceptor);
}
let promise = Promise.resolve(request);
// Make the actual request...
promise = promise
.then((request) => {
// Deconstruct the request, since fetch-mock doesn't actually support fetch(Request);
const init = {
method: request.method,
headers: request.headers
};
if (['GET', 'HEAD'].indexOf(request.method) === -1 && request.body) {
init.body = request.body;
// Loop through the request interceptors, constructing a promise chain.
for (let interceptor of this.requestInterceptors) {
promise = promise.then(interceptor);
}
// Make the actual request...
promise = promise
.then((request) => {
// Deconstruct the request, since fetch-mock doesn't actually support fetch(Request);
const init = {
method: request.method,
headers: request.headers
};
if (['GET', 'HEAD'].indexOf(request.method) === -1 && request.body) {
init.body = request.body;
}
return fetch(request.url, init);
});
// Fetch will treat all http responses (2xx, 3xx, 4xx, 5xx, etc) as successful responses.
// This will catch all 4xx and 5xx responses and return them to the catch() handler. Note
// that it's up to the downstream developer to determine whether what they received is an
// error or a failed response.
promise.then((response) => {
if (response.status >= 400) {
return reject(response);
} else {
return response;
}
return fetch(request.url, init);
});
// Pass the response content through the response interceptors...
for (let interceptor of this.responseInterceptors) {
promise = promise.then(interceptor);
}
// Pass the response content through the response interceptors...
for (let interceptor of this.responseInterceptors) {
promise = promise.then(interceptor);
}
return promise;
promise.then((response) => resolve(response), (error) => reject(error));
});
}
/**

View File

@ -189,21 +189,17 @@ describe('Http', () => {
});
});
it("should pass failed requests back to the invoker", (done) => {
it("should pass failed requests to the catch block.", (done) => {
fetchMock.get(testUrl, {status: 500, body: testResponse});
http.httpGet(testUrl)
.then((response) => {
// The HTTP request 'succeeded' with a failing state.
expect(response.status).toEqual(500);
return response.json();
})
.then((body) => {
expect(body).toEqual(testResponse);
expect(response).toBeNull();
done();
})
.catch((error) => {
expect(error).toBeNull();
.catch((response) => {
expect(response.status).toBe(500);
done();
});
});