yanyue404 / blog

Just blog and not just blog.
https://yanyue404.github.io/blog/
Other
88 stars 13 forks source link

通用接口配置:Axios or Fetch #194

Open yanyue404 opened 3 years ago

yanyue404 commented 3 years ago

前言

在项目的开发过程中,我们也少不了与后台服务器进行数据的获取和交互,这一般都是通过接口完成的,那么我们如何进行合理的通用接口配置呢?

需求分析

期望:

使用:

import { _get, _post, _put, _delete } from '@/utils/fetch';

export const getArticleList = (p) => _get('/cms-api/api/cms/getVideoList', p);

export const addNewArticle = (p) => _post('/cms-api/api/cms/addNewArticle', p);

export const updateArticle = (p) => _put('/cms-api/api/cms/updateArticle', p);

export const deleteArticle = (p) =>
  _delete('/cms-api/api/cms/deleteArticle', p);

实现思路

axios 是 前端请求库的集大成者,api 设计对于我们二次封装极为方便。

fetch API 在兼容库fetch polyfill的支持下,封装也十分的遍历。

下面,我们选择这两种方案来进行封装前端公共请求。

请求方式 —— axios

// 环境的切换
axios.defaults.baseURL = '/prod-api';
axios.defaults.timeout = 60000;
axios.defaults.headers.post['Content-Type'] =
  'application/x-www-form-urlencoded;charset=UTF-8;';

const LOADING = (window.LOADING = new loadingControl());

// 请求拦截器
axios.interceptors.request.use(
  (config) => {
    config.headers['x-tenant-header'] = 'web-spcloud-sales';

    LOADING.addLoading();

    Toast.loading({
      message: '加载中...',
      forbidClick: true,
      loadingType: 'spinner',
      duration: 60000,
    });

    const token = sessionStorage.getItem('ACC_SIGN') || '';
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token; // 让每个请求携带自定义token 请根据实际情况自行修改
    } else {
      // 客戶端以及客戶端秘钥
      config.headers['Authorization'] =
        'Basic ' + window.btoa('web-spcloud-sales:webApp');
    }
    return config;
  },
  (error) => {
    return Promise.error(error);
  },
);

// 响应拦截器
axios.interceptors.response.use(
  (response) => {
    LOADING.isCloseLoading();
    LOADING.loadingCount === 0 ? Toast.clear() : '';

    if (response.status === 200) {
      return Promise.resolve(response);
    } else {
      return Promise.reject(response);
    }
  },
  // 服务器状态码不是2开头的的情况
  // 处理错误状态码,例如登录过期提示,错误提示等等
  (error) => {
    LOADING.reset();
    Toast.clear();
    if (error.response.status) {
      switch (error.response.status) {
        case 401:
          this.$toast({ message: '用户未授权', time: 2000 });
          break;
        // 403 token过期
        // 登录过期对用户进行提示
        // 清除本地token和清空vuex中token对象
        // 跳转登录页面
        case 403:
          Toast.fail({
            message: '登录过期,请重新登录',
            duration: 3000,
          });
          break;
        // 404请求不存在
        case 404:
          Toast.fail({
            message: '网络请求不存在...',
            duration: 3000,
          });
          break;
        // 其他错误,直接抛出错误提示
        default:
          Toast.fail({
            message: '未知系统异常...',
            duration: 3000,
          });
      }
      return Promise.reject(error.response);
    }
  },
);

export function _get(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .get(url, {
        params: params,
      })
      .then((res) => {
        resolve(res.data);
      })
      .catch((err) => {
        console.log(err);
        reject(err.data);
      });
  });
}

export function _post(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .post(url, params)
      .then((res) => {
        resolve(res.data);
      })
      .catch((err) => {
        reject(err.data);
      });
  });
}

export function _put(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .put(url, QS.stringify(params))
      .then((res) => {
        resolve(res.data);
      })
      .catch((err) => {
        reject(err.data);
      });
  });
}

export function _delete(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .delete(url, QS.stringify(params))
      .then((res) => {
        resolve(res.data);
      })
      .catch((err) => {
        reject(err.data);
      });
  });
}

请求方式 —— fetch

import 'whatwg-fetch';
import { Toast } from 'vant';
import loadingControl from './loadingControl';

const baseURL = '/prod-api';
const LOADING = (window.LOADING = new loadingControl());

export default class Http {
  static async request(method, url, data) {
    const config = {
      method: method,
      headers: {
        'Content-Type': 'application/json',
        'x-tenant-header': 'web-spcloud-sales',
      },
    };

    const token = sessionStorage.getItem('ACC_SIGN') || '';
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token;
    } else {
      // 客戶端以及客戶端秘钥
      config.headers['Authorization'] =
        'Basic ' + window.btoa('web-spcloud-sales:webApp');
    }

    if (method === 'GET') {
      url = baseURL + url + this.formatQuery(data);
    } else {
      url = baseURL + url;
      config['body'] = JSON.stringify(data);
    }

    LOADING.addLoading();
    Toast.loading({
      message: '加载中...',
      forbidClick: true,
      loadingType: 'spinner',
      duration: 55000,
    });

    return fetch(url, config)
      .then((response) => this.isSuccess(response))
      .then((response) => {
        return response.json();
      });
  }

  // 判断请求是否成功
  static isSuccess(res) {
    LOADING.isCloseLoading();
    LOADING.loadingCount === 0 ? Toast.clear() : '';
    if (res.status >= 200 && res.status < 300) {
      return res;
    } else {
      this.requestException(res);
    }
  }

  // 处理异常
  static requestException(res) {
    const error = new Error(res.statusText);
    error.response = res;
    throw error;
  }

  // url处理
  static formatQuery(query) {
    let params = [];
    if (query) {
      for (let item in query) {
        let vals = query[item];
        if (vals !== undefined) {
          params.push(item + '=' + query[item]);
        }
      }
    }
    return params.length ? '?' + params.join('&') : '';
  }

  static get(url, data) {
    return this.request('GET', url, data);
  }

  static post(url, data) {
    return this.request('POST', url, data);
  }

  static put(url, data) {
    return this.request('PUT', url, data);
  }

  static delete(url, data) {
    return this.request('DELETE', url, data);
  }
}

export function _get(url, params) {
  return new Promise((resolve, reject) => {
    Http.get(url, params)
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

export function _post(url, params) {
  return new Promise((resolve, reject) => {
    Http.post(url, params)
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

export function _put(url, params) {
  return new Promise((resolve, reject) => {
    Http.post(url, params)
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

export function _delete(url, params) {
  return new Promise((resolve, reject) => {
    Http.delete(url, params)
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

模块化组合

这里看我的另一篇文章

参考