axios 封装

axios 的封装

理解

最开始我们学习axios时,我们的代码也许是这样的:

import axios from 'axios'

export function request(config) {
  const instance = axios.create({
    baseURL: 'http://127.0.0.1:8888/api/private/v1/',
    timeout: 5000
  })
  // 请求拦截
  instance.interceptors.request.use(config => {
    config.headers.Authorization = 'xxx'
    return config;
  }, err => {
    console.log(err);
  })
  // 响应拦截
  instance.interceptors.response.use(res => {
    return res;
  }, err => {
    console.log(err);
  })
  // 发送网络请求
  return instance(config);
}

上述代码非常简单,axios.create() 创建一个实例,注册请求、响应拦截函数。

随着我们不断学习、工作就会发现一个问题:那就是项目中可能会存在多个后台服务器,比如项目里A模块是自己公司的后端人员提供的服务接口,但是B模块比较复杂,自己公司没有能力或没打算去做,就会交给别的公司去做。这就意味着,我们前端在一个项目中,需要对接多个不同的后台服务器接口。

所以,上面的代码就不怎么适用了。我们需要对axios进行更灵活的封装。

因为出现了多个后台服务器,我们应该创建多个不同的实例,又因为出现了多个实例,就应该考虑这个请求、响应拦截应该怎么写?

本文的拦截思想是对每一次请求进行三次拦截:

  • 创建的所有实例的所有请求都统一会执行的拦截器
  • 创建的某一个实例的所有请求统一会执行的拦截器
  • 针对某一个请求单独会执行的拦截器

换句话说,发送一次请求,其请求会拦截三次,响应也会拦截三次。这样就非常灵活了,比如可以单独为不同的实例设置不同baseURL,timeout,也可以针对不同后台服务接口返回的字段结构进行响应拦截处理。

下面直接看代码吧~

代码

新建一个单独的request.js文件,这里写了一个类Request。

import axios from 'axios'
import { ElLoading } from 'element-plus'

const DEFAULT_SHOW_LOADING = false

class Request {
  constructor(config) {
    this.instance = axios.create(config)
    this.interceptors = config.interceptors ?? {}
    this.showLoading = config.showLoading ?? DEFAULT_SHOW_LOADING
    this.instanceShowLoading = this.showLoading
    this.loadingInstance = null

    this.addInterceptors()
  }

  /**
   * 添加拦截器
   */
  addInterceptors() {
    // 所有实例的拦截器
    this.instance.interceptors.request.use(
      config => {
        console.log(3);
        // 请求中动画...
        if (this.showLoading === true) {
          this.loadingInstance = ElLoading.service({
            lock: true,
            text: '加载中...',
            background: 'rgba(0,0,0,.5)'
          })
        }

        return config
      },
      error => {
        this.loadingInstance?.close()
        console.log('所有实例请求拦截器Error:', error);
        return Promise.reject(error)
      }
    )
    this.instance.interceptors.response.use(
      response => {
        console.log(4);
        this.loadingInstance?.close()
        return response.data
      },
      error => {
        this.loadingInstance?.close()
        console.log('所有实例响应拦截器Error:', error);
        return Promise.reject(error)
      }
    )

    // 当前实例的拦截器
    const { requestInterceptor, requestInterceptorCatch, responseInterceptor, responseInterceptorCatch } = this.interceptors
    this.instance.interceptors.request.use(requestInterceptor, requestInterceptorCatch)
    this.instance.interceptors.response.use(responseInterceptor, responseInterceptorCatch)
  }

  /**
   * 请求方法
   * 封装了针对当个请求的拦截器
   */
  request(config) {
    const {
      showLoading,
      interceptors: {
        requestInterceptor,
        responseInterceptor,
        responseInterceptorCatch
      } = {}
    } = config

    if (requestInterceptor) {
      config = requestInterceptor(config)
    }

    // 添加或取消loading
    if (typeof showLoading === 'boolean') {
      this.showLoading = showLoading
    }

    return new Promise((resolve, reject) => {

      this.instance.request(config)
        .then(response => {
          if (responseInterceptor) {
            response = responseInterceptor(response)
          }
          // 恢复loading设置
          this.resetShowLoading()

          resolve(response)

        })
        .catch(error => {
          if (responseInterceptorCatch) {
            error = responseInterceptorCatch()
          }

          // 恢复loading设置
          this.resetShowLoading()

          reject(error)

        })
    })

  }

  /**
   * 常用请求方法别名
   */
  get(config) {
    return this.request({ ...config, method: 'get' })
  }
  post(config) {
    return this.request({ ...config, method: 'post' })
  }
  delete(config) {
    return this.request({ ...config, method: 'delete' })
  }
  patch(config) {
    return this.request({ ...config, method: 'patch' })
  }

  /**
   * 恢复loading设置
   */
  resetShowLoading() {
    this.showLoading = this.instanceShowLoading
  }

}

export default Request

再新建一个文件使用Request类,实例化对象就类似于 axios.create() 得到一个不同的实例。

import Request from "./request";

const request = new Request({
  baseURL: 'https://pagead2.googlesyndication.com/',
  timeout: '5000',
  headers: {
    'token': 'xxx'
  },
  interceptors: {
    requestInterceptor(config) {
      console.log(2);
      return config
    },
    requestInterceptorCatch(error) {
      console.log('requset实例请求拦截Error:', error);
      return Promise.reject(error)
    },
    responseInterceptor(response) {
      console.log(5);
      return response
    },
    responseInterceptorCatch(error) {
      console.log('requset实例响应拦截Error:', error);
      return Promise.reject(error)
    },
  },
  showLoading: true
})


export {
  request
}

上述代码只实例化了一次,其实是根据业务可以实例化多次的。

再来看如何使用

import { request } from './api/request'
setTimeout(() => {
  request.get({
    url: 'getconfig/sodar?sv=200&tid=gda&tv=r20230306&st=env',
    interceptors: {
      requestInterceptor(config) {
        console.log(1);
        return config
      },
      responseInterceptor(res) {
        console.log(6);
        return res
      }
    },
    showLoading: false
  }).then(res => {
    console.log(res, 'res');
  })
    .catch(err => {
      console.log(err, 'err');
    })
}, 3000);

setTimeout(() => {
  request.get({
    url: 'getconfig/sodar?sv=200&tid=gda&tv=r20230306&st=env',
    interceptors: {
      requestInterceptor(config) {
        console.log(1);
        return config
      },
      responseInterceptor(res) {
        console.log(6);
        return res
      }
    },
    // showLoading: false
  }).then(res => {
    console.log(res, 'res');
  })
    .catch(err => {
      console.log(err, 'err');
    })
}, 6000);

上述代码拿着实例 request发送了两次请求,只是为了测试 showLoading噢,细节大家自己去看。

对了,代码中在三次请求拦截和三次响应拦截中console.log打印了1-6,大家可以运行起来,看看执行顺序。我这里先告诉你结论:1 2 3 4 5 6

注意this.instance.interceptors.request.usethis.instance.interceptors.response.use可以多次注册拦截响应函数,先注册的请求拦截后执行,先注册的响应拦截先执行。

代码里还用了 element-plus UI库,大家可以自行替换,或者删除。如果跟我一样用element-plus的话,有一个坑得注意:

ElLoading.service() loading效果的样式必须手动引入(不知道是不是我用的插件自动按需引入element-plus的原因),当然,这个跟axios的封装没啥关系,具体怎么做看你们自己了。

手动引入代码

import 'element-plus/es/components/loading/style/css'

重点

重点理解拦截器,为什么要拦截三次。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容