封装一个简易 fetch 请求库

Huy大约 2 分钟javascriptjavascript

/**
 * 封装一个 fetch 请求
 * url 请求地址
 * method 请求方式 * GET POST PUT DELETE OPTIONS
 * credentials 携带资源凭证 * include same-origin * omit * credentials
 * headers: null 自定义的请求头信息 「格式必须是纯粹对象」
 * body:null 请求主体信息「只针对 POST 系列请求, 根据当前服务器要求,如果用户传递的是一个纯粹对象, 则需要把其变为 urlencoded 格式字符串(设定请求头中的 Content-Type) 」
 * params: null 设定问号传参信息「格式必须是纯粹对象, 在内部把其拼接到 url 的末尾」
 * responseType: 'json' 请求响应的数据类型 * json text blob arrayBuffer
 * timeout: 5000 请求超时时间
 * signal: 中断请求的信号
 */
const http = function (config) {
  if (typeof config !== 'object') config = {}

  config = Object.assign(
    {
      url: '',
      method: 'GET',
      credentials: 'include',
      headers: null,
      body: null,
      params: null,
      responseType: 'json',
      timeout: 5000,
      signal: null,
    },
    config
  )

  // 必要参数判断
  if (!config.url) {
    throw new Error('url is required')
  }
  if (typeof config.headers !== 'object') config.headers = {}
  if (config.params !== null && typeof config.params !== 'object')
    config.params = null

  // 处理细节
  let {
    url,
    method,
    credentials,
    headers,
    body,
    params,
    responseType,
    timeout,
  } = config

  // 处理问号传参
  if (params) {
    let paramsStr = Object.keys(params)
      .map((key) => `${key}=${params[key]}`)
      .join('&')
    url += `${url.includes('?') ? '&' : '?'}${paramsStr}` // 拼接时是否带问号
  }

  // 处理请求主体信息
  if (typeof body === 'object') {
    body = JSON.stringify(body)
    headers['Content-Type'] = 'application/json'
  }

  const token = localStorage.getItem('token')
  if (token) {
    headers['Authorization'] = `Bearer ${token}`
  }

  // 超时处理
  if (signal === null) {
    signal = new AbortController().signal
  }
  const timeoutId = setTimeout(() => {
    signal.abort()
  }, timeout)

  // 发起请求
  method = method.toUpperCase()
  config = {
    method,
    credentials,
    headers,
    cache: 'no-cache',
    signal,
  }

  if (['POST', 'PUT', 'PATCH'].includes(method)) {
    config.body = body
  }

  return fetch(url, config)
    .then((response) => {
      const { status, statusText } = response
      if (status >= 200 && status < 300) {
        // 请求成功
        let result

        switch (responseType.toLowerCase()) {
          case 'json':
            result = response.json()
            break
          case 'text':
            result = response.text()
            break
          case 'blob':
            result = response.blob()
            break
          case 'arraybuffer':
            result = response.arrayBuffer()
            break
          default:
            result = response.json()
        }

        return response.json()
      }

      // 请求失败: HTTP 状态码失败
      return Promise.reject({
        code: -100,
        status,
        statusText,
      })
    })
    .catch((reason) => {
      const { code, status } = reason
      if (code === -100) {
        switch (+status) {
          case 401:
            console.error('未授权,请重新登录!')
            break
          case 403:
            console.error('禁止访问!')
            break
          case 404:
            console.error('请求的资源不存在!')
            break
          case 500:
            console.error('服务器内部错误,请稍后再试!')
            break
          default:
            console.error('当前网络繁忙,请稍后再试!')
        }
      }
      return Promise.reject(reason)
    })
}

// 快捷方法
// ["GET", "HEAD", "DELETE", "OPTIONS"].forEach(method => {
//   http[method.toLowerCase()] = function(url, config) {
//     if (typeof config !== 'object') {
//       config['url'] = url
//       config['method'] = method
//       return http(config)
//     }
//   }
// })

// ["POST", "PUT", "PATCH"].forEach(method => {
//   http[method.toLowerCase()] = function(url, body, config) {
//     if (typeof config !== 'object') {
//       config['url'] = url
//       config['method'] = method
//       config["body"] = body;
//       return http(config)
//     }
//   }
// })

export default http
Loading...