Merge pull request #30 from pleshevskiy/bug-29

refac: move abort controller to lazy request hook
This commit is contained in:
Dmitriy Pleshevskiy 2021-06-22 22:55:13 +03:00 committed by GitHub
commit 1a8ed09d85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 53 deletions

View File

@ -3,18 +3,19 @@ import { Method } from './endpoint';
import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './misc';
export interface ClientConfig {
baseUrl: string,
readonly baseUrl: string,
}
export interface PrepareRequestProps {
url: string,
method: Method,
headers: Record<string, string>,
variables: Record<string, any> | FormData,
readonly url: string,
readonly method: Method,
readonly headers: Record<string, string>,
readonly variables: Record<string, any> | FormData,
}
export type RequestProps<R> = PrepareRequestProps & {
transformResponseData?: (data: unknown) => R,
export interface RequestProps<R> extends PrepareRequestProps {
readonly transformResponseData?: (data: unknown) => R,
readonly abortSignal: AbortSignal,
}
export type ResponseWithError =
@ -26,8 +27,6 @@ export type ClientResponse<Data extends Record<string, any>> =
& Readonly<{ data: Data }>
export class Client {
private controller = new AbortController();
constructor(private readonly config: ClientConfig) {}
prepareRequest(props: PrepareRequestProps) {
@ -82,12 +81,13 @@ export class Client {
request<Data extends Record<string, any>>(
{
transformResponseData,
abortSignal,
...restProps
}: RequestProps<Data>
): Promise<ClientResponse<Data>> {
const req = this.prepareRequest(restProps);
return fetch(req, { signal: this.controller.signal })
return fetch(req, { signal: abortSignal })
// TODO: need to check response headers and parse json only if content-type header is application/json
.then(
(res) => Promise.all([res, res.json()]),
@ -131,9 +131,4 @@ export class Client {
return res;
});
}
cancelRequest() {
this.controller.abort();
this.controller = new AbortController();
}
}

View File

@ -53,6 +53,8 @@ export function useLazyRequest<E extends AnyEndpoint>(
);
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState<LazyRequestHandlerConfig<E> | null>(null);
const abortControllerRef = React.useRef(new AbortController());
const transformResponseData = React.useCallback(
(data: unknown): ExtractEndpointResponse<E> => {
return isFunction(endpoint.transformResponseData) ?
@ -114,6 +116,7 @@ export function useLazyRequest<E extends AnyEndpoint>(
return client
.request<ExtractEndpointResponse<E>>({
...endpoint,
abortSignal: abortControllerRef.current.signal,
url: endpointUrl,
headers,
variables,
@ -141,7 +144,7 @@ export function useLazyRequest<E extends AnyEndpoint>(
[state, config, client, endpoint, defaultHeaders, transformResponseData]
);
const refetch = React.useCallback(
const refetchRequest = React.useCallback(
() => {
if (prevHandlerConfig != null) {
handler({
@ -153,14 +156,15 @@ export function useLazyRequest<E extends AnyEndpoint>(
[handler, prevHandlerConfig]
);
const cancelRequest = React.useCallback(() => {
dispatch({ type: 'cancel' });
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
}, []);
React.useEffect(
() => {
return () => {
dispatch({ type: 'cancel' });
client.cancelRequest();
};
},
[client]
() => cancelRequest,
[cancelRequest]
);
return [
@ -171,8 +175,8 @@ export function useLazyRequest<E extends AnyEndpoint>(
isCalled: state.isCalled,
isCanceled: state.isCanceled,
fetchError: state.fetchError,
refetch,
cancel: client.cancelRequest.bind(client),
refetch: refetchRequest,
cancel: cancelRequest,
},
];
}

21
target/client.d.ts vendored
View File

@ -1,16 +1,17 @@
import { Method } from './endpoint';
export interface ClientConfig {
baseUrl: string;
readonly baseUrl: string;
}
export interface PrepareRequestProps {
url: string;
method: Method;
headers: Record<string, string>;
variables: Record<string, any> | FormData;
readonly url: string;
readonly method: Method;
readonly headers: Record<string, string>;
readonly variables: Record<string, any> | FormData;
}
export interface RequestProps<R> extends PrepareRequestProps {
readonly transformResponseData?: (data: unknown) => R;
readonly abortSignal: AbortSignal;
}
export declare type RequestProps<R> = PrepareRequestProps & {
transformResponseData?: (data: unknown) => R;
};
export declare type ResponseWithError = Pick<Response, 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'headers' | 'url'> & Readonly<{
error?: Error;
canceled?: boolean;
@ -20,9 +21,7 @@ export declare type ClientResponse<Data extends Record<string, any>> = ResponseW
}>;
export declare class Client {
private readonly config;
private controller;
constructor(config: ClientConfig);
prepareRequest(props: PrepareRequestProps): Request;
request<Data extends Record<string, any>>({ transformResponseData, ...restProps }: RequestProps<Data>): Promise<ClientResponse<Data>>;
cancelRequest(): void;
request<Data extends Record<string, any>>({ transformResponseData, abortSignal, ...restProps }: RequestProps<Data>): Promise<ClientResponse<Data>>;
}

View File

@ -15,7 +15,6 @@ import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './mis
export class Client {
constructor(config) {
this.config = config;
this.controller = new AbortController();
}
prepareRequest(props) {
var _a;
@ -47,9 +46,9 @@ export class Client {
});
}
request(_a) {
var { transformResponseData } = _a, restProps = __rest(_a, ["transformResponseData"]);
var { transformResponseData, abortSignal } = _a, restProps = __rest(_a, ["transformResponseData", "abortSignal"]);
const req = this.prepareRequest(restProps);
return fetch(req, { signal: this.controller.signal })
return fetch(req, { signal: abortSignal })
// TODO: need to check response headers and parse json only if content-type header is application/json
.then((res) => Promise.all([res, res.json()]), (err) => {
const canceled = err.name === 'AbortError';
@ -89,8 +88,4 @@ export class Client {
return res;
});
}
cancelRequest() {
this.controller.abort();
this.controller = new AbortController();
}
}

View File

@ -14,6 +14,7 @@ export function useLazyRequest(endpoint, config) {
isCalled: false,
});
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState(null);
const abortControllerRef = React.useRef(new AbortController());
const transformResponseData = React.useCallback((data) => {
return isFunction(endpoint.transformResponseData) ?
endpoint.transformResponseData(data)
@ -50,7 +51,7 @@ export function useLazyRequest(endpoint, config) {
dispatch({ type: 'call', headers, variables, params });
setPrevHandlerConfig(handlerConfig !== null && handlerConfig !== void 0 ? handlerConfig : {});
return client
.request(Object.assign(Object.assign({}, endpoint), { url: endpointUrl, headers,
.request(Object.assign(Object.assign({}, endpoint), { abortSignal: abortControllerRef.current.signal, url: endpointUrl, headers,
variables,
transformResponseData }))
.then((response) => {
@ -65,17 +66,17 @@ export function useLazyRequest(endpoint, config) {
return null;
});
}, [state, config, client, endpoint, defaultHeaders, transformResponseData]);
const refetch = React.useCallback(() => {
const refetchRequest = React.useCallback(() => {
if (prevHandlerConfig != null) {
handler(Object.assign(Object.assign({}, prevHandlerConfig), { force: true }));
}
}, [handler, prevHandlerConfig]);
React.useEffect(() => {
return () => {
dispatch({ type: 'cancel' });
client.cancelRequest();
};
}, [client]);
const cancelRequest = React.useCallback(() => {
dispatch({ type: 'cancel' });
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
}, []);
React.useEffect(() => cancelRequest, [cancelRequest]);
return [
handler,
{
@ -84,8 +85,8 @@ export function useLazyRequest(endpoint, config) {
isCalled: state.isCalled,
isCanceled: state.isCanceled,
fetchError: state.fetchError,
refetch,
cancel: client.cancelRequest.bind(client),
refetch: refetchRequest,
cancel: cancelRequest,
},
];
}