Coder Social home page Coder Social logo

deer-develop / return-fetch Goto Github PK

View Code? Open in Web Editor NEW
120.0 0.0 6.0 846 KB

⛓️ A simple and powerful high order function to extend fetch for baseUrl, default headers, and interceptors.

Home Page: https://return-fetch.myeongjae.kim

License: MIT License

JavaScript 2.90% Shell 6.13% TypeScript 90.89% Makefile 0.08%
fetch interceptor json request response

return-fetch's People

Contributors

myeongjae-kim avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

return-fetch's Issues

[refactor] mergeRequestObjectWithRequestInit function

hi im big fan of your library : )

i have a little question of mergeRequestObjectWithRequestInit function

  • Why did you use ArrayBuffer to handle request object?

I think it works well just use only the request object

AS-IS

const mergeRequestObjectWithRequestInit = (
  request: Request,
  requestInit?: RequestInit,
): Promise<RequestInit> => {
  const mergedRequest = new Request(request, requestInit);
  return new Response(mergedRequest.body).arrayBuffer().then((body) => ({
    method: mergedRequest.method,
    headers: mergedRequest.headers,
    body: body,
    referrer: mergedRequest.referrer,
    referrerPolicy: mergedRequest.referrerPolicy,
    mode: mergedRequest.mode,
    credentials: mergedRequest.credentials,
    cache: mergedRequest.cache,
    redirect: mergedRequest.redirect,
    integrity: mergedRequest.integrity,
    keepalive: mergedRequest.keepalive,
    signal: mergedRequest.signal,
    window: requestInit?.window,
  }));
};

TO-BE

const mergeRequestObjectWithRequestInit = (
  request: Request,
  requestInit?: RequestInit,
): RequestInit => {
  const newRequest = new Request(request, requestInit);
  return {
    method: newRequest.method,
    headers: newRequest.headers,
    body: newRequest.body,
    referrer: newRequest.referrer,
    referrerPolicy: newRequest.referrerPolicy,
    mode: newRequest.mode,
    credentials: newRequest.credentials,
    cache: newRequest.cache,
    redirect: newRequest.redirect,
    integrity: newRequest.integrity,
    keepalive: newRequest.keepalive,
    signal: newRequest.signal,
    window: requestInit?.window,
  };
};

If you think this code is okay, can I make a pr?

have a good day :)

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

안녕하세요, 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'));
  }
};

[refactor] mergeRequestObjectWithRequestInit function

hi, I came back with a solution to this issue :)

I think we need to use arrayBuffer to reuse Request body objects.

But I think it's a little awkward to create a response object to reuse the Request body object.

so.. how about refactor it like this??

AS-IS

const mergeRequestObjectWithRequestInit = (
  request: Request,
  requestInit?: RequestInit,
): Promise<RequestInit> => {
  const mergedRequest = new Request(request, requestInit);
  return new Response(mergedRequest.body).arrayBuffer().then((body) => ({
    method: mergedRequest.method,
    headers: mergedRequest.headers,
    body: body,
    referrer: mergedRequest.referrer,
    referrerPolicy: mergedRequest.referrerPolicy,
    mode: mergedRequest.mode,
    credentials: mergedRequest.credentials,
    cache: mergedRequest.cache,
    redirect: mergedRequest.redirect,
    integrity: mergedRequest.integrity,
    keepalive: mergedRequest.keepalive,
    signal: mergedRequest.signal,
    window: requestInit?.window,
  }));
};

TO-BE

const mergeRequestObjectWithRequestInit = (
  request: Request,
  requestInit?: RequestInit,
): RequestInit => {
  const mergedRequest = new Request(request, requestInit);
  
 const bufferedRequestBody = await mergedRequest.arrayBuffer()

  return {
    body: bufferedRequestBody,
    cache: mergedRequest.cache,
    credentials: mergedRequest.credentials,
    headers: mergedRequest.headers,
    integrity: mergedRequest.integrity,
    keepalive: mergedRequest.keepalive,
    method: mergedRequest.method,
    mode: mergedRequest.mode,
    redirect: mergedRequest.redirect,
    referrer: mergedRequest.referrer,
    referrerPolicy: mergedRequest.referrerPolicy,
    signal: mergedRequest.signal,
    window: requestInit?.window,
  }
};

from the mdn docs request object also has arrayBuffer methods, so if we use this, i think it will be looks better.

how about your think?

have a good day :)

export `JsonRequestInit` type

Problem

I'm building a custom Fetch HOC on top of return-fetch-json and it receives the same params, so I need this type to reuse it from the same package where it comes from instead of creating it on my function

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.