From f109d87b415f3342b44a1288c2b54d6b830cb82c Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Tue, 22 Jun 2021 22:45:02 +0300 Subject: [PATCH] refac: move abort controller to lazy request hook --- src/client.ts | 25 ++++++++++--------------- src/lazy_request_hook.ts | 24 ++++++++++++++---------- target/client.d.ts | 21 ++++++++++----------- target/client.js | 9 ++------- target/lazy_request_hook.js | 21 +++++++++++---------- 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/client.ts b/src/client.ts index 1bf4ecb..5cfbcad 100644 --- a/src/client.ts +++ b/src/client.ts @@ -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, - variables: Record | FormData, + readonly url: string, + readonly method: Method, + readonly headers: Record, + readonly variables: Record | FormData, } -export type RequestProps = PrepareRequestProps & { - transformResponseData?: (data: unknown) => R, +export interface RequestProps extends PrepareRequestProps { + readonly transformResponseData?: (data: unknown) => R, + readonly abortSignal: AbortSignal, } export type ResponseWithError = @@ -26,8 +27,6 @@ export type ClientResponse> = & 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>( { transformResponseData, + abortSignal, ...restProps }: RequestProps ): Promise> { 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(); - } } diff --git a/src/lazy_request_hook.ts b/src/lazy_request_hook.ts index 6fae4a3..87acc67 100644 --- a/src/lazy_request_hook.ts +++ b/src/lazy_request_hook.ts @@ -53,6 +53,8 @@ export function useLazyRequest( ); const [prevHandlerConfig, setPrevHandlerConfig] = React.useState | null>(null); + const abortControllerRef = React.useRef(new AbortController()); + const transformResponseData = React.useCallback( (data: unknown): ExtractEndpointResponse => { return isFunction(endpoint.transformResponseData) ? @@ -114,6 +116,7 @@ export function useLazyRequest( return client .request>({ ...endpoint, + abortSignal: abortControllerRef.current.signal, url: endpointUrl, headers, variables, @@ -141,7 +144,7 @@ export function useLazyRequest( [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( [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( isCalled: state.isCalled, isCanceled: state.isCanceled, fetchError: state.fetchError, - refetch, - cancel: client.cancelRequest.bind(client), + refetch: refetchRequest, + cancel: cancelRequest, }, ]; } diff --git a/target/client.d.ts b/target/client.d.ts index 7966a3a..da10bd2 100644 --- a/target/client.d.ts +++ b/target/client.d.ts @@ -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; - variables: Record | FormData; + readonly url: string; + readonly method: Method; + readonly headers: Record; + readonly variables: Record | FormData; +} +export interface RequestProps extends PrepareRequestProps { + readonly transformResponseData?: (data: unknown) => R; + readonly abortSignal: AbortSignal; } -export declare type RequestProps = PrepareRequestProps & { - transformResponseData?: (data: unknown) => R; -}; export declare type ResponseWithError = Pick & Readonly<{ error?: Error; canceled?: boolean; @@ -20,9 +21,7 @@ export declare type ClientResponse> = ResponseW }>; export declare class Client { private readonly config; - private controller; constructor(config: ClientConfig); prepareRequest(props: PrepareRequestProps): Request; - request>({ transformResponseData, ...restProps }: RequestProps): Promise>; - cancelRequest(): void; + request>({ transformResponseData, abortSignal, ...restProps }: RequestProps): Promise>; } diff --git a/target/client.js b/target/client.js index daf2d90..1a7e251 100644 --- a/target/client.js +++ b/target/client.js @@ -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(); - } } diff --git a/target/lazy_request_hook.js b/target/lazy_request_hook.js index f5a5acb..42a7bfb 100644 --- a/target/lazy_request_hook.js +++ b/target/lazy_request_hook.js @@ -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, }, ]; }