Skip to content
Open
174 changes: 173 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { lazy } from "react";
import { Route, Routes, Navigate } from "react-router-dom";
import IndividualPurchase from "./pages/IndividualPurchase";
import DetailMovie from "./pages/DetailMovie";
import SearchList from "./pages/SearchList";

const DetailMovie = lazy(() => import("./pages/DetailMovie"));
const IndividualPurchase = lazy(() => import("./pages/IndividualPurchase"));
const SearchList = lazy(() => import("./pages/SearchList"));

function App() {
return (
Expand Down
2 changes: 2 additions & 0 deletions src/apis/fetchSearchMovie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const searchMovie = (title: string) =>
query: title,
},
responseInterceptor: (res) => convertSnakeToCamel(res),
maxRetries: 5,
retryDelay: 2000,
});

export const useSearchMovieQuery = (title: string) =>
Expand Down
78 changes: 51 additions & 27 deletions src/apis/tmdbRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ interface RequestOptions {
requestBody?: Record<string, unknown> | string;
timeout?: number;
signal?: AbortSignal;
maxRetries?: number;
retryDelay?: number;
keepalive?: boolean;
}

type RequestInterceptor = (config: RequestOptions) => RequestOptions;
Expand All @@ -21,6 +24,8 @@ type RequestConfig = RequestOptions & {
timeout: number;
};

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export const tmdbRequest = async ({
method = "GET",
endpoint,
Expand All @@ -30,6 +35,9 @@ export const tmdbRequest = async ({
signal,
requestInterceptor,
responseInterceptor,
maxRetries = 3,
retryDelay = 1000,
keepalive = false,
}: tmdbRequestType) => {
let config: RequestConfig = {
method,
Expand All @@ -54,37 +62,53 @@ export const tmdbRequest = async ({
const url = queryString
? `${TMDB_BASE_URL}${config.endpoint}?${queryString}`
: `${TMDB_BASE_URL}${config.endpoint}`;
let retryCount = 0;

try {
const response = (await Promise.race([
fetch(url, {
method: config.method,
headers: API_OPTIONS.headers,
body: config.requestBody
? JSON.stringify(config.requestBody)
: undefined,
signal: abortSignal,
}),
new Promise((_, reject) => {
setTimeout(() => {
controller?.abort();
reject(new Error(`요청 시간 초과: ${timeout}ms`));
}, config.timeout);
}),
])) as Response;

if (!response.ok) {
throw new Error(`API 요청 실패: ${response.status}`);
}
while (true) {
try {
const response = (await Promise.race([
fetch(url, {
method: config.method,
headers: API_OPTIONS.headers,
body: config.requestBody
? JSON.stringify(config.requestBody)
: undefined,
signal: abortSignal,
keepalive,
}),
new Promise((_, reject) => {
setTimeout(() => {
controller?.abort();
reject(new Error(`요청 시간 초과: ${timeout}ms`));
}, config.timeout);
}),
])) as Response;

if (!response.ok) {
throw new Error(`API 요청 실패: ${response.status}`);
}

const data = await response.json();

return responseInterceptor ? responseInterceptor(data) : data;
} catch (error: unknown) {
if (error instanceof Error && error.name === "AbortError") {
throw new Error("요청이 취소되었습니다.");
}

if (retryCount >= maxRetries) {
console.error(`${maxRetries}번 재시도 했지만 실패했습니다:`, error);
throw error;
}

const backoffDelay = retryDelay * Math.pow(2, retryCount);

retryCount++;

const data = await response.json();
await wait(backoffDelay);

return responseInterceptor ? responseInterceptor(data) : data;
} catch (error: unknown) {
if (error instanceof Error && error.name === "AbortError") {
throw new Error("요청이 취소되었습니다.");
continue;
}
throw error;
}
};

Expand Down
Loading
Loading