Skip to content

Commit

Permalink
feat: Add per request error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
MattCCC committed Aug 12, 2021
1 parent 25843fd commit 42c865b
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 16 deletions.
60 changes: 56 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Package was originally written to accomodate many API requests in an orderly fas
- [Accessing Axios instance](#accessing-axios-instance)
- [Global Settings](#global-settings)
- [Per Endpoint Settings](#per-endpoint-settings)
- [Full TypeScript support](#full-typescript-support)
- [Advanced example](#advanced-example)
- [TypeScript support](#full-typescript-support)
- [Examples](#examples)
- [Support & collaboration](#support-collaboration)


Expand Down Expand Up @@ -87,7 +87,12 @@ const api = createApiFetcher({
// You can await for your request. Check "strategy" for details. response returns data directly
const response = await api.getUserDetails({ userId: 1 });

const response = await api.updateUserDetails({ name: 'Mark' }, { userId: 1 });
console.log('User details', response);

await api.updateUserDetails({ name: 'Mark' }, { userId: 1 });

console.log('User details updated');

```
In this basic example we fetch data from an API for user with an ID of 1. We also update user's name to Mark. If you prefer OOP you can import `ApiHandler` and initialize the handler using `new ApiHandler()` instead.

Expand Down Expand Up @@ -143,6 +148,7 @@ Each endpoint in `apiEndpoints` is an object that accepts properties below. You
| rejectCancelled | boolean | `false` | If `true` and request is set to `cancellable`, a cancelled request promise will be rejected. By default instead of rejecting the promise, `defaultResponse` from global options is returned. |
| defaultResponse | any | `null` | Default response when there is no data or when endpoint fails depending on a chosen `strategy` |
| strategy | string | | You can control strategy per each request. Global strategy is applied by default. |
| onError | function | | You can specify a function that will be triggered when an endpoint fails. |

## Full TypeScript support

Expand Down Expand Up @@ -182,7 +188,53 @@ api.fetchMovies( { newMovies: 1 } );

Package ships interfaces with responsible defaults making it easier to add new endpoints. It exposes `Endpoints` and `Endpoint` types.

## Advanced example
## Examples
### Per Request Error handling

```typescript
import { createApiFetcher } from 'axios-multi-api';

const api = createApiFetcher({
apiUrl: 'https://example.com/api',
apiEndpoints: {
sendMessage: {
method: 'get',
url: '/send-message/:postId',
},
},
});

async function sendMessage() {
await api.sendMessage({ message: 'Something..' }, { postId: 1 }, {
onError(error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
}
});

console.log('Message sent successfully');
}

sendMessage();

```


### OOP style with custom Error Handler (advanced)

You could for example create an API service class that extends the handler, inject an error service class to handle with a store that would collect the errors.

Expand Down
27 changes: 17 additions & 10 deletions src/http-request-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ErrorHandlingStrategy,
RequestHandlerConfig,
EndpointConfig,
RequestError,
} from './types/http-request-handler';

/**
Expand Down Expand Up @@ -137,7 +138,7 @@ export class HttpRequestHandler implements MagicalClass {
* @param {string} url Url
* @param {*} data Payload
* @param {EndpointConfig} config Config
* @throws {Error} If request fails
* @throws {RequestError} If request fails
* @returns {Promise} Request response or error info
*/
public __get(prop: string) {
Expand All @@ -154,7 +155,7 @@ export class HttpRequestHandler implements MagicalClass {
* @param {string} url Url
* @param {*} data Payload
* @param {EndpointConfig} config Config
* @throws {Error} If request fails
* @throws {RequestError} If request fails
* @returns {Promise} Request response or error info
*/
public prepareRequest(type: Method, url: string, data: any = null, config: EndpointConfig = null): Promise<IRequestResponse> {
Expand Down Expand Up @@ -190,14 +191,20 @@ export class HttpRequestHandler implements MagicalClass {
/**
* Process global Request Error
*
* @param {Error} error Error instance
* @param {RequestError} error Error instance
* @param {EndpointConfig} requestConfig Per endpoint request config
* @returns {AxiosInstance} Provider's instance
*/
protected processRequestError(error: Error): void {
protected processRequestError(error: RequestError, requestConfig: EndpointConfig): void {
if (axios.isCancel(error)) {
return;
}

// Invoke per request "onError" call
if (requestConfig.onError && typeof requestConfig.onError === 'function') {
requestConfig.onError(error);
}

const errorHandler = new HttpRequestErrorHandler(
this.logger,
this.httpRequestErrorService
Expand All @@ -209,11 +216,11 @@ export class HttpRequestHandler implements MagicalClass {
/**
* Output error response depending on chosen strategy
*
* @param {Error} error Error instance
* @param {RequestError} error Error instance
* @param {EndpointConfig} requestConfig Per endpoint request config
* @returns {AxiosInstance} Provider's instance
*/
protected async outputErrorResponse(error: Error, requestConfig: EndpointConfig): Promise<IRequestResponse> {
protected async outputErrorResponse(error: RequestError, requestConfig: EndpointConfig): Promise<IRequestResponse> {
const isRequestCancelled = requestConfig.cancelToken && axios.isCancel(error);
const errorHandlingStrategy = requestConfig.strategy || this.strategy;

Expand All @@ -240,11 +247,11 @@ export class HttpRequestHandler implements MagicalClass {
/**
* Output error response depending on chosen strategy
*
* @param {Error} error Error instance
* @param {RequestError} error Error instance
* @param {EndpointConfig} requestConfig Per endpoint request config
* @returns {*} Error response
*/
public isRequestCancelled(error: Error, requestConfig: EndpointConfig): boolean {
public isRequestCancelled(error: RequestError, requestConfig: EndpointConfig): boolean {
return requestConfig.cancelToken && axios.isCancel(error);
}

Expand Down Expand Up @@ -293,7 +300,7 @@ export class HttpRequestHandler implements MagicalClass {
* @param {string} payload.url Request url
* @param {*} payload.data Request data
* @param {EndpointConfig} payload.config Request config
* @throws {Error}
* @throws {RequestError}
* @returns {Promise} Response Data
*/
protected async handleRequest({
Expand All @@ -314,7 +321,7 @@ export class HttpRequestHandler implements MagicalClass {
try {
response = await this.requestInstance.request(requestConfig);
} catch (error) {
this.processRequestError(error);
this.processRequestError(error, requestConfig);

return this.outputErrorResponse(error, requestConfig);
}
Expand Down
7 changes: 5 additions & 2 deletions src/types/http-request-handler.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";

export type IRequestResponse<T = any> = Promise<AxiosResponse<T>>;

export type InterceptorCallback = (value: AxiosRequestConfig) => AxiosRequestConfig | Promise<AxiosRequestConfig>;

export type ErrorHandlingStrategy = 'throwError' | 'reject' | 'silent' | 'defaultResponse';

export type RequestError = AxiosError<any>;

export interface EndpointConfig extends AxiosRequestConfig {
cancellable?: boolean;
rejectCancelled?: boolean;
strategy?: ErrorHandlingStrategy;
onError?: (error: RequestError) => any;
}

export interface RequestHandlerConfig extends EndpointConfig {
flattenResponse?: boolean;
defaultResponse?: any;
logger?: any;
onError?: any;
onError?: (error: RequestError) => any;
}

export interface APIHandlerConfig extends RequestHandlerConfig {
Expand Down

0 comments on commit 42c865b

Please sign in to comment.