封装fetch请求

2,196 阅读2分钟

背景

在大型项目中,大量的请求,大量重复的代码,让请求封装成为一种必然。

步骤

想要的接口使用方式:


async function getData(params){
    return await requestGet(api,params)
}

getData({id:1}).then(res=>{
    // 得到接口数据res即data
})

// 或者

let res = await getData({id:1})

requestGet就是要实现的接口封装。

要做的事情

  1. 封装fetch方法
  2. 在请求之前携带headers、content-type、cookie,get请求将参数带在url后面,post请求将参数携带在body参数
  3. 请求过程当中,注意网络错误捕获并抛出
  4. 请求正常时,将数据返回给开发者,异常时,统一捕获错误,并抛出

代码实现

import { addDomain } from '@/env';
import onion from './onion.js';
import { message } from 'antd';

/*
 ** 兼容两种情况
 ** 1.帮助用户处理错误信息,用户得到的直接就是data数据
 ** 2.用户自己处理错误信息,我们把请求所有res返回
 */
function isReturnAllData(obj, res) {
  const { OPTIONS } = obj;
  if (OPTIONS && OPTIONS.getAll) {
    return true;
  } else {
    if (res.msg && res.ret !== '0') {
      message.error(res.msg);
    }
    return false;
  }
}

export async function request(obj) {
  let res = await onion(obj);
  return isReturnAllData(obj, res) ? res : res.data;
}

export async function requestGetI(url, params, OPTIONS) {
  url = addDomain(url);
  return await request({ url, options: { method: 'get', params }, OPTIONS });
}

export async function requestPostI(url, params, OPTIONS) {
  url = addDomain(url);
  return await request({ url, options: { method: 'post', body: params }, OPTIONS });
}

// onion.js

import { formatQueryObj } from '@/common/utils';
import { message } from 'antd';

// 用reduce实现promise的串行
export default function onion(obj) {
  return [frontInterCept, fetcher, endInterCept].reduce((prev, curr) => {
    return prev
      .then((res) => curr(res))
      .catch((err) => {
        message.error(err);
      });
  }, Promise.resolve(obj));
}

// 前置拦截
function frontInterCept(obj) {
  return new Promise((resolve, reject) => {
    let optionFinal = {};

    const { url, options } = obj;
    const { method, params, body } = options;
    let headers = {};
    if (body) {
      if (body instanceof window.FormData) {
        /* 单独处理请求参数中存在二进制文件的情况
         ** 此时不需要加content-type
         ** 浏览器发现是二进制文件,会自动加content-type和boundary
         ** 手动加content-type的话 boundary中的内容会丢失
         */
      } else {
        // 普通post请求
        headers = { 'content-type': 'application/json; charset=utf-8' };
      }
    } else {
      // get请求
      headers = { 'content-type': 'application/x-www-form-urlencoded; charset=utf-8' };
    }

    optionFinal = method === 'post' ? { headers, method, body: JSON.stringify(body) } : { method, params };
    // 允许跨域携带cookie
    optionFinal.credentials = 'include';
    // 允许跨域请求
    optionFinal.mode = 'cors';

    const tmpQuery = formatQueryObj(params);

    let urlI = tmpQuery ? url + (url.indexOf('?') !== -1 ? '&' : '?' + tmpQuery) : url;

    return resolve({ url: urlI, optionFinal });
  });
}

// 后置拦截
function endInterCept(obj = {}) {
  const { ret, data } = obj;
  return new Promise((resolve, reject) => {
    switch (ret) {
      case '3': {
        // 跳转登录页面
        return reject('未登录');
      }
      default: {
        /*
         ** 单独处理登录,未登录直接跳转
         ** 其他错误,将接口所有数据返回,在request那边统一处理
         */
        return resolve(obj);
      }
    }
  });
}

async function fetcher(obj = {}) {
  return new Promise((resolve, reject) => {
    const { url, optionFinal } = obj;
    window
      .fetch(url, optionFinal)
      .then((res) => {
        if (res.status !== 200) {
          return reject('网络错误,请稍后重试~');
        } else {
          // fetch返回的是一个流对象
          res
            .json()
            .then((res) => {
              console.log('res', res);
              return resolve(res);
            })
            .catch((err) => {
              return reject('网络错误,请稍后重试~');
            });
        }
      })
      .catch((err) => {
        return reject('网络错误,请稍后重试~');
      });
  });
}


效果:

兼容处理开发者是否需要自己处理错误信息的情况,默认帮他处理

关于请求content-type看这里