Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

사용법 관련 문의드립니다. #3

Closed
H0onnn opened this issue Jan 16, 2024 · 1 comment
Closed

사용법 관련 문의드립니다. #3

H0onnn opened this issue Jan 16, 2024 · 1 comment

Comments

@H0onnn
Copy link

H0onnn commented Jan 16, 2024

안녕하세요, Nextjs를 공부하다 블로그에 작성해주신 return-fetch 에 대한 게시글을 보고 프로젝트에 적용해보던 중,

문의 사항이 생겨 이슈 남깁니다.

아래와 같이 코드를 작성해보았는데, queryParamsrequestBody 는 접근이 불가능 하더라구요.

제가 최종적으로 사용하고자 하는 코드는

// use request example

const response = await request<void>({
  method: 'put',
  url: '/members/vehicles/me',
  requestBody: body,
});
return response?.data;

이런 식으로 사용하고싶은데, 어떻게 코드를 작성해야 할까요 ? 아래는 제가 작성한 코드입니다 !

import returnFetch, {
  ReturnFetch,
  FetchArgs,
  ReturnFetchDefaultOptions,
} from 'return-fetch';
import { get, set } from 'lodash';
import qs from 'qs';
import { useCustomerStore } from '@/store/customer';
import { RequestParams } from './types';
import logger from '@/utils/logger';

// req body를 json으로 받고, res body를 json으로 반환하는 fetch
type JsonRequestInit = Omit<NonNullable<FetchArgs[1]>, 'body'> & {
  body?: object;
  queryParams?: Record<string, unknown>;
};

export type ResponseGenericBody<T> = Omit<
  Awaited<ReturnType<typeof fetch>>,
  keyof Body | 'clone'
> & {
  body: T;
};

export type JsonResponse<T> = T extends object
  ? ResponseGenericBody<T>
  : ResponseGenericBody<unknown>;

const parseJsonSafely = (text: string): object | string => {
  try {
    return JSON.parse(text);
  } catch (e) {
    if ((e as Error).name !== 'SyntaxError') {
      throw e;
    }

    return text.trim();
  }
};

export const returnFetchJson = (args?: ReturnFetchDefaultOptions) => {
  const fetch = returnFetch(args);

  return async <T>(
    url: FetchArgs[0],
    init?: JsonRequestInit
  ): Promise<JsonResponse<T>> => {
    const response = await fetch(url, {
      ...init,
      body: init?.body && JSON.stringify(init.body),
    });

    const body = parseJsonSafely(await response.text()) as T;

    return {
      headers: response.headers,
      ok: response.ok,
      redirected: response.redirected,
      status: response.status,
      statusText: response.statusText,
      type: response.type,
      url: response.url,
      body,
    } as JsonResponse<T>;
  };
};

// reposne가 400이상의 status를 받는 경우 에러 throw
const returnFetchThrowingErrorByStatusCode: ReturnFetch = (args) =>
  returnFetch({
    ...args,
    interceptors: {
      response: async (response) => {
        if (response.status >= 400) {
          throw await response.text().then(Error);
        }

        return response;
      },
    },
  });

export const client = returnFetchJson({
  fetch: returnFetchThrowingErrorByStatusCode({
    baseUrl: process.env.NEXT_PUBLIC_API_URL,
    headers: { Accept: 'application/json' },
    querySerializer: (queryParams) => {
      return qs.stringify(queryParams, { arrayFormat: 'brackets' });
    },
    interceptors: {
      request: async (requestArgs) => {
        const [url, init] = requestArgs;

        logger('--- Request 시작 ---');
        logger(`> Request url: ', ${url}`);
        logger(`> Request: ', ${init}`);

        const { authorization } = useCustomerStore.getState();
        logger(`> Authorization: ', ${authorization}`);

        if (authorization) {
          logger('> Set Authorization: ', authorization);
          set(requestArgs, 'headers.authorization', authorization);
        }
        set(requestArgs, 'headers.service-name', 'CUSTOMER_APP');
        logger('--- Request 종료 ---');

        return requestArgs;
      },
      response: async (response, requestArgs) => {
        const { headers, url, status, statusText } = response;
        const [_, requestInit] = requestArgs;

        const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}${url}`;
        const authorization = get(headers, 'authorization');

        logger('--- Response 시작 ---');
        if (authorization) {
          logger('## Save authorization via response: ', authorization);
          useCustomerStore.getState().saveAuthorization(authorization);
        }
        logger(`< Response [${requestInit?.method}]: ${apiUrl}`);
        logger(`< Response [${status}]: ${statusText ?? ''}`);
        logger(`< Response Headers: ${JSON.stringify(headers)}`);
        logger(response);
        logger('--- Response 종료 ---');

        return response;
      },
    },
  }),
});

const fetchResponseOnRejected = (error: Error) => {
  const method = get(error, 'config.method');
  const url = `${process.env.NEXT_PUBLIC_API_URL}${get(error, 'config.url')}`;
  logger(`< Response Error [${method}]: ${url}`);
  logger(`< Response Error: `, JSON.stringify(get(error, 'response.error')));
  if (get(error, 'response.status') === 403) {
    useCustomerStore.getState().clearCustomer();
  }
  logger('--- Response 에러 ---');
  return Promise.reject(error);
};

export const request = async <T>({
  method,
  url,
  queryParams,
  requestBody,
  isMultipart,
}: RequestParams) => {
  let headers = {};
  if (isMultipart) {
    headers = {
      'Content-Type': 'multipart/form-data',
    };
  }

  switch (method) {
    case 'get':
      return client(url, { method, headers, queryParams }).catch(
        fetchResponseOnRejected
      );
    case 'post':
      return client(url, { method, headers, requestBody, queryParams }).catch(
        fetchResponseOnRejected
      );
    case 'put':
      return client(url, { method, headers, requestBody, queryParams }).catch(
        fetchResponseOnRejected
      );
    case 'delete':
      return client(url, { method, headers, requustBody }).catch(
        fetchResponseOnRejected
      );
    default:
      return Promise.reject(new Error('Invalid method'));
  }
};
@H0onnn H0onnn closed this as completed Jan 19, 2024
@myeongjae-kim
Copy link
Contributor

@H0onnn 안녕하세요!

fetch는 axios와 다르게 query parameter를 별도의 파라미터로 처리하지 않고 url 뒤에 ?a=b 형태로 사용합니다 (보통의 URL에서 보던 query parameter와 동일함)

코드를 보니 returnFetchJson의 첫 번째 매개변수에 querySerializer property를 넣으셨는데, querySerializer는 라이브러리에서 지원하지 않는 parameter라서 아무 의미가 없을 것 같네요.

코드 마지막의 request 함수에서 queryParams를 serialize해서 url 뒤에 붙여주면 될 것 같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants