feat: parsing body for failed ajax responses (#2039)
This commit is contained in:
parent
5eafa1ffc1
commit
7a875ef1b4
5 changed files with 85 additions and 10 deletions
5
.changeset/quiet-insects-shake.md
Normal file
5
.changeset/quiet-insects-shake.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/ajax': minor
|
||||
---
|
||||
|
||||
Parses response body automatically for fetchJson failed responses. Add test for reading out the response body in cases of a failed response in regular fetch call.
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -22384,7 +22384,7 @@
|
|||
},
|
||||
"packages/ajax": {
|
||||
"name": "@lion/ajax",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.4",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/singleton-manager": {
|
||||
|
|
|
|||
|
|
@ -124,9 +124,10 @@ export class Ajax {
|
|||
*
|
||||
* @param {RequestInfo} info
|
||||
* @param {RequestInit & Partial<CacheRequestExtension>} [init]
|
||||
* @param {Boolean} [parseErrorResponse]
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
async fetch(info, init) {
|
||||
async fetch(info, init, parseErrorResponse = false) {
|
||||
const request = /** @type {CacheRequest} */ (new Request(info, { ...init }));
|
||||
request.cacheOptions = init?.cacheOptions;
|
||||
request.params = init?.params;
|
||||
|
|
@ -137,7 +138,11 @@ export class Ajax {
|
|||
const response = /** @type {CacheResponse} */ (interceptedRequestOrResponse);
|
||||
response.request = request;
|
||||
if (isFailedResponse(interceptedRequestOrResponse)) {
|
||||
throw new AjaxFetchError(request, response);
|
||||
throw new AjaxFetchError(
|
||||
request,
|
||||
response,
|
||||
parseErrorResponse ? await this.__attemptParseFailedResponseBody(response) : undefined,
|
||||
);
|
||||
}
|
||||
// prevent network request, return cached response
|
||||
return response;
|
||||
|
|
@ -149,7 +154,11 @@ export class Ajax {
|
|||
const interceptedResponse = await this.__interceptResponse(response);
|
||||
|
||||
if (isFailedResponse(interceptedResponse)) {
|
||||
throw new AjaxFetchError(request, interceptedResponse);
|
||||
throw new AjaxFetchError(
|
||||
request,
|
||||
response,
|
||||
parseErrorResponse ? await this.__attemptParseFailedResponseBody(response) : undefined,
|
||||
);
|
||||
}
|
||||
return interceptedResponse;
|
||||
}
|
||||
|
|
@ -163,8 +172,7 @@ export class Ajax {
|
|||
*
|
||||
* @param {RequestInfo} info
|
||||
* @param {LionRequestInit} [init]
|
||||
* @template T
|
||||
* @returns {Promise<{ response: Response, body: T }>}
|
||||
* @returns {Promise<{ response: Response, body: string|Object }>}
|
||||
*/
|
||||
async fetchJson(info, init) {
|
||||
const lionInit = {
|
||||
|
|
@ -183,7 +191,18 @@ export class Ajax {
|
|||
|
||||
// typecast LionRequestInit back to RequestInit
|
||||
const jsonInit = /** @type {RequestInit} */ (lionInit);
|
||||
const response = await this.fetch(info, jsonInit);
|
||||
const response = await this.fetch(info, jsonInit, true);
|
||||
|
||||
const body = await this.__parseBody(response);
|
||||
|
||||
return { response, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
* @returns {Promise<string|Object>}
|
||||
*/
|
||||
async __parseBody(response) {
|
||||
let responseText = await response.text();
|
||||
|
||||
const { jsonPrefix } = this.__config;
|
||||
|
|
@ -191,7 +210,6 @@ export class Ajax {
|
|||
responseText = responseText.substring(jsonPrefix.length);
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
let body = responseText;
|
||||
|
||||
if (
|
||||
|
|
@ -207,8 +225,21 @@ export class Ajax {
|
|||
} else {
|
||||
body = responseText;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
return { response, body };
|
||||
/**
|
||||
* @param {Response} response
|
||||
* @returns {Promise<string|Object|undefined>}
|
||||
*/
|
||||
async __attemptParseFailedResponseBody(response) {
|
||||
let body;
|
||||
try {
|
||||
body = await this.__parseBody(response);
|
||||
} catch (e) {
|
||||
// no need to throw/log, failed responses often don't have a body
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ export class AjaxFetchError extends Error {
|
|||
/**
|
||||
* @param {Request} request
|
||||
* @param {Response} response
|
||||
* @param {string|Object} [body]
|
||||
*/
|
||||
constructor(request, response) {
|
||||
constructor(request, response, body) {
|
||||
super(`Fetch request to ${request.url} failed.`);
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,25 @@ describe('Ajax', () => {
|
|||
}
|
||||
expect(thrown).to.be.true;
|
||||
});
|
||||
|
||||
it('throws on 4xx responses, will allow parsing response manually', async () => {
|
||||
ajax.addRequestInterceptor(async () => new Response('my response', { status: 400 }));
|
||||
|
||||
let thrown = false;
|
||||
try {
|
||||
await ajax.fetch('/foo');
|
||||
} catch (e) {
|
||||
// https://github.com/microsoft/TypeScript/issues/20024 open issue, can't type catch clause in param
|
||||
const _e = /** @type {AjaxFetchError} */ (e);
|
||||
expect(_e).to.be.an.instanceOf(AjaxFetchError);
|
||||
expect(_e.request).to.be.an.instanceOf(Request);
|
||||
expect(_e.response).to.be.an.instanceOf(Response);
|
||||
const body = await _e.response.text();
|
||||
expect(body).to.equal('my response');
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchJson', () => {
|
||||
|
|
@ -183,6 +202,24 @@ describe('Ajax', () => {
|
|||
expect(response.body).to.eql({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
it('throws on 4xx responses, but still attempts parsing response body when using fetchJson', async () => {
|
||||
ajax.addRequestInterceptor(async () => new Response('my response', { status: 400 }));
|
||||
|
||||
let thrown = false;
|
||||
try {
|
||||
await ajax.fetchJson('/foo');
|
||||
} catch (e) {
|
||||
// https://github.com/microsoft/TypeScript/issues/20024 open issue, can't type catch clause in param
|
||||
const _e = /** @type {AjaxFetchError} */ (e);
|
||||
expect(_e).to.be.an.instanceOf(AjaxFetchError);
|
||||
expect(_e.request).to.be.an.instanceOf(Request);
|
||||
expect(_e.response).to.be.an.instanceOf(Response);
|
||||
expect(_e.body).to.equal('my response');
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).to.be.true;
|
||||
});
|
||||
|
||||
describe('given a request body', () => {
|
||||
it('encodes the request body as json', async () => {
|
||||
await ajax.fetchJson('/foo', { method: 'POST', body: { a: 1, b: 2 } });
|
||||
|
|
|
|||
Loading…
Reference in a new issue