Fetch Adapter
Published: 2023-08-09 09:02:28

```ts
import type { AxiosPromise, AxiosRequestConfig, AxiosResponse } from 'axios';

import { AxiosError } from 'axios';
import CanceledError from 'axios/unsafe/cancel/CanceledError';
import buildFullPath from 'axios/unsafe/core/buildFullPath';
import settle from 'axios/unsafe/core/settle';
import buildURL from 'axios/unsafe/helpers/buildURL';

export interface AxiosFetchRequestConfig extends AxiosRequestConfig<BodyInit> {
  mode?: RequestMode;
  body?: BodyInit;
  cache?: RequestCache;
  integrity?: string;
  redirect?: RequestRedirect;
  referrer?: string;
  credentials?: RequestCredentials;
}

export default function fetchAdapter(config: AxiosFetchRequestConfig): AxiosPromise {
  return new Promise((resolve, reject) => {
    const controller = new AbortController();
    const signal = controller.signal;
    const request = createRequest(config, signal);
    const promises = [getResponse(request, config)];

    if (config.timeout && config.timeout > 0) {
      promises.push(timeoutHandle(request, controller, config));
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        reject(!cancel ? new CanceledError(null, config, request) : cancel);
        controller.abort();
      });
    }

    return Promise.race(promises)
      .then(data => settle(resolve, reject, data))
      .catch(reject);
  });
}

function timeoutHandle(request: Request, controller: AbortController, config: AxiosFetchRequestConfig): Promise<Error> {
  return new Promise(res => {
    setTimeout(() => {
      controller.abort();
      const message = config.timeoutErrorMessage
        ? config.timeoutErrorMessage
        : 'timeout of ' + config.timeout + 'ms exceeded';

      res(createError(message, config, 'ECONNABORTED', request));
    }, config.timeout);
  });
}

async function getResponse(request: Request, config: AxiosFetchRequestConfig) {
  let stageOne;

  try {
    stageOne = await fetch(request);
  } catch (e) {
    return createError('Network Error', config, 'ERR_NETWORK', request);
  }

  const response = {
    ok: stageOne.ok,
    status: stageOne.status,
    statusText: stageOne.statusText,
    headers: new Headers(stageOne.headers),
    config: config,
    request,
  } as unknown as AxiosResponse<any, any>;

  if (stageOne.status >= 200 && stageOne.status !== 204) {
    switch (config.responseType) {
      case 'arraybuffer':
        response.data = await stageOne.arrayBuffer();
        break;
      case 'blob':
        response.data = await stageOne.blob();
        break;
      case 'json':
        response.data = await stageOne.json();
        break;
      default:
        response.data = await stageOne.text();
        break;
    }
  }

  return response;
}

function createRequest(config: AxiosFetchRequestConfig, signal: AbortSignal): Request {
  const headers = new Headers(config.headers as any as Headers);

  if (config.auth) {
    const username = config.auth.username || '';
    const password = config.auth.password ? decodeURI(encodeURIComponent(config.auth.password)) : '';

    headers.set('Authorization', `Basic ${btoa(username + ':' + password)}`);
  }

  const method = config.method.toUpperCase();
  const options: RequestInit = { headers, method, signal };

  if (method !== 'GET' && method !== 'HEAD') {
    options.body = config.data;
  }

  if (config.mode) {
    options.mode = config.mode;
  }

  if (config.cache) {
    options.cache = config.cache;
  }

  if (config.integrity) {
    options.integrity = config.integrity;
  }

  if (config.redirect) {
    options.redirect = config.redirect;
  }

  if (config.referrer) {
    options.referrer = config.referrer;
  }

  if (typeof config.withCredentials !== 'undefined') {
    options.credentials = config.withCredentials ? 'include' : 'omit';
  }

  const fullPath = buildFullPath(config.baseURL, config.url);
  const url = buildURL(fullPath, config.params, config.paramsSerializer);

  // Expected browser to throw error if there is any wrong configuration value
  return new Request(url, options);
}

function createError(
  message: string,
  config: AxiosFetchRequestConfig,
  code: string,
  request: Request,
  response?: AxiosResponse,
): Error {
  return new AxiosError(message, AxiosError[code], config as any, request, response);
}

```