Axios package, including basic package and some non-essential packages (custom method, monitor upload/download progress, interrupt request, interface loading)

foreword

Axios is one of the basic tools for front-end development, and its packaging is not new.
This article is divided into two parts: one is the basic packaging example of axios; the other is non-essential packaging, listing some more practical packaging requirements encountered in personal development.
The examples in this article are based on[email protected]


1. Basic package

There are many basic packages of axios on the Internet, and the content is not bad. Here, refer to the axios official documentation and the axios package of the GitHub Gaoxing open source project:

axios - Interceptors - github
vue-element-admin

import axios from 'axios'
import store from '@/store'
import {
    
     getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
    
    
  // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// Add a request interceptor
service.interceptors.request.use(
  config => {
    
    
    // do something before request is sent
    if (store.getters.token) {
    
    
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    
    
    // do something with request error
    return Promise.reject(error)
  }
)

// Add a response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
   */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    
    
    // do something with response data
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
    
    
      // TODO: Message prompt
      console.error(res.message || 'Error')

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
    
    
        // TODO: to re-login
      }
      // reject
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
    
    
      return res
    }
  },
  error => {
    
    
    // do something with response error
    return Promise.reject(error)
  }
)

export default service

In the example, baseURL, message prompt, message confirmation, and token acquisition need to be replaced with specific items.
The encapsulation is very simple, that is, create an axios instance, set baseURL/timeout, add request interceptor and response interceptor.
Bring the user login token in the request interceptor, and identify the special response (login failure/timeout) in the response interceptor according to the code in the response data (as agreed with the backend), and handle it accordingly. Use the encapsulated method
:

import request from '@/utils/request'

export function fetchList(query) {
    
    
  return request({
    
    
    url: '/vue-element-admin/article/list',
    method: 'get',
    params: query
  })
}

In the above example, request is the axios instance created and exported in request.js. Compared with directly importing and using the default axios instance, the parameter types of the two are the same, and you can also use aliases .get .postsuch as
The above encapsulation completes the most basic and important functions. The thrown instance is the same as axios, but every user who uses this instance will automatically add a login token to the request header, and automatically intercept the request and response. Perfect!

summary

The most basic package is like this, which can be understood as:

import axios from 'axios'

const service = axios.create(config)
service.interceptors.request.use(requestInterceptor)
service.interceptors.response.use(responseInterceptor)

export default service

Among them, configis the default configuration, requestInterceptorand responseInterceptorare request interceptor and response interceptor respectively. The above examples are for reference only, and the implementation details can be adjusted according to specific project requirements.

Please refer to the axios official website document
axios - Request Config
axios - Interceptors


2. Other non-essential packages

The previous section on the basic package is enough. The content of this section is based on the basic package, supplementing some non-essential requirements, and these non-essential requirements may never be used in some projects. Implementation process varies from person to person

Use case based on[email protected]

custom method

If there is a certain type of interface that needs to be fixed to add/adjust the axios configuration, it may cause code redundancy. We hope that the method only contains the url and data related to the interface. At this point, it can be packaged as follows:

// ...

const axiosBlob = (url, data, otherConfigs = {
     
     }) => {
    
    
  otherConfigs.responseType = 'blob'
  otherConfigs.timeout = 5000
  return new Promise((resolve, reject) => {
    
    
    service({
    
    
      method: 'get',
      url,
      params: data,
      ...otherConfigs
    }).then(resolve, reject)
  })
}

const axiosPostFormData = (url, data, otherConfigs = {
     
     }) => {
    
    
  otherConfigs.headers = {
    
     'Content-Type': 'multipart/form-data; charset=UTF-8' }
  return new Promise((resolve, reject) => {
    
    
    service({
    
    
      method: 'post',
      url,
      data,
      ...otherConfigs
    }).then(resolve, reject)
  })
}

export {
    
     service, axiosBlob, axiosPostFormData }
export default service

use

import request, {
    
     axiosBlob } from '@/utils/request'

export function fetchFile1(params) {
    
    
  return request({
    
    
    url: '/vue-element-admin/article/file',
    method: 'get',
    params,
    responseType: 'blob',
    timeout: 5000
  })
}

export function fetchFile2(params) {
    
    
  return axiosBlob('/vue-element-admin/article/file', params)
}

As above, you can export custom methods to avoid repeatedly filling in fixed configuration information under specific requests.
If you prefer this style, you can uniformly encapsulate get/post/patch/put/delete requests. Others are the same as the two special requests in the above example. You can add it yourself.

// ...

const axiosCustomFuncHandler = (method, url, data, otherConfigs = {
     
     }) => {
    
    
  return new Promise((resolve, reject) => {
    
    
    service({
    
    
      method,
      url,
      [method === 'get' ? 'params' : 'data']: data ? data : {
    
    },
      ...otherConfigs,
    }).then(resolve, reject)
  })
}
const axiosGet = (url, data, otherConfigs) => axiosCustomFuncHandler('get', url, data, otherConfigs)
const axiosPost = (url, data, otherConfigs) => axiosCustomFuncHandler('post', url, data, otherConfigs)
const axiosPut = (url, data, otherConfigs) => axiosCustomFuncHandler('put', url, data, otherConfigs)
const axiosPatch = (url, data, otherConfigs) => axiosCustomFuncHandler('patch', url, data, otherConfigs)
const axiosDelete = (url, otherConfigs) => axiosCustomFuncHandler('delete', url, undefined, otherConfigs)
const axiosPostFormData = (url, data, otherConfigs = {
     
     }) => {
    
    
  otherConfigs.headers = {
    
     'Content-Type': 'multipart/form-data; charset=UTF-8' }
  return axiosCustomFuncHandler('post', url, data, otherConfigs)
}
const axiosBlob = (url, data, otherConfigs = {
     
     }) => {
    
    
  otherConfigs.responseType = 'blob'
  otherConfigs.timeout = 5000
  return axiosCustomFuncHandler('get', url, data, otherConfigs)
}

export {
    
    
  service,
  axiosGet,
  axiosPost,
  axiosPut,
  axiosPatch,
  axiosDelete,
  axiosBlob,
  axiosPostFormData,
}
export default service

Please note the difference between custom method and instance method alias:
axios instance method: request(config)
axios instance method alias: request.get(url[, config])
custom method:axiosGet(url, params, config)

config priority

axios - Config Defaults

In the custom method, a fixed request configuration is set to the axios instance. The axios default instance can also set the default configuration, and in the axios instance method, the request configuration can also be passed. There is a priority between them:
Global axios defaults<<Custom instance defaultsConfig argument for the request

Monitor upload/download progress

Axios provides events to monitor upload/download progress: axios - Request Config

  // `onUploadProgress` allows handling of progress events for uploads
  // browser only
  onUploadProgress: function (progressEvent) {
    
    
    // Do whatever you want with the native progress event
  },

  // `onDownloadProgress` allows handling of progress events for downloads
  // browser only
  onDownloadProgress: function (progressEvent) {
    
    
    // Do whatever you want with the native progress event
  },

You can see that their parameter types are the same ( ProgressEvent )
. The most direct way to use it is to import the packaged service instance and define the listening method

<script setup>
  import request from '@/utils/request'
  import {
    
     ref, onMounted } from 'vue'

  let progress = ref(0)

  onMounted(() => {
    
    
    request({
    
    
      url: '/vue-element-admin/article/list',
      method: 'get',
      params: query,
      onDownloadProgress: function (progressEvent) {
    
    
        if (progressEvent.lengthComputable) {
    
    
          const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
          progress.value = percentCompleted
        } else {
    
    
          progress.value = 100
        }
      }
    }).then(res => {
    
    
      // ...
    })
  })
</script>
<template>
  <div>Loading...{
    
    {
    
     progress }}%</div>
</template>

If everyone who needs to monitor the download progress writes it like this, on the one hand, it will generate a lot of redundant code, and on the other hand, it is not convenient to uniformly maintain the monitoring method

Idea:
Pass a responsive variable (download/upload progress) request configto , and bind the listening event in the request interceptor. Listening to events changes the value of reactive variables

// 下载进度监听事件(更新封装方法传入的响应式变量——进度)
const handleDownloadProcess = (progressEvent, progress) => {
    
    
  if (progressEvent.lengthComputable) {
    
    
    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
    progress.value = percentCompleted
  } else {
    
    
    progress.value = 100
  }
}
// 上传进度监听事件,同 handleDownloadProcess
const handleUploadProcess = handleDownloadProcess

// request interceptor
service.interceptors.request.use(
  config => {
    
    
    // ...

    // set download/upload progress' event listeners
    if (config.downloadProgress) {
    
    
      config.onDownloadProgress = progressEvent =>
        handleDownloadProcess(progressEvent, config.downloadProgress)
    }
    if (config.uploadProgress) {
    
    
      config.onUploadProgress = progressEvent =>
        handleUploadProcess(progressEvent, config.uploadProgress)
    }
    return config
  },
  error => {
    
    
    return Promise.reject(error)
  }
)

In the above example, two configuration names (downloadProgress, uploadProgress) are agreed, and the listener event is bound by judging whether the corresponding variables exist.
For example, if you want to bind the download progress monitoring event, you need to pass the variable request configin downloadProgress. To be more precise, it is best to check whether it is a responsive variable in the request interceptor.
Since it is processed in the request interceptor of the instance, whether it is directly calling the instance or the encapsulated method, the download progress monitoring can be realized. Compared with manually binding listener events, the writing method is as follows:

request({
    
    
  url: '/vue-element-admin/article/list',
  method: 'get',
  params: query,
  downloadProgress: progress
}).then(res => {
    
    
  // ...
})

interface loading

An article I saw on the Nuggets is a kind of encapsulation for the interface loading state: the story that axios and loading have to tell.
It targets the following business scenarios:

const loading = ref(false)

function getData () {
    
    
  loading.value = true
  axios.get('/vue-element-admin/article/list').then(res => {
    
    
    // ...
  }).finally(() => {
    
    
    loading.value = false
  })
}

I never thought about encapsulating interface loading before, maybe it can extract very little public code.
Idea: pass
a responsive variable (loading) to the axios instance, change it to true in the request interceptor (indicating that the request interface is started), and change it to false in the response interceptor (indicating that the interface response is complete)request config

// request interceptor
service.interceptors.request.use(
  config => {
    
    
    // ...
    if (config.loading) {
    
    
      config.loading.value = true
    }
    return config
  },
  error => {
    
    
    return Promise.reject(error)
  }
)
// response interceptor
service.interceptors.response.use(
  response => {
    
    
    if (response.config.loading) {
    
    
      response.config.loading.value = false
    }
    // ...
  },
  error => {
    
    
    if (error.config.loading) {
    
    
      error.config.loading.value = false
    }
    return Promise.reject(error)
  }
)

use:

const loading = ref(false)

function getData () {
    
    
  axios.get('/vue-element-admin/article/list', {
    
     loading }).then(res => {
    
    
    // ...
  })
}

interrupt request

Sometimes, for performance reasons, we hope to actively interrupt the ongoing request of axios. For example, routing jumps.
Axios provides two methods to interrupt requests. For details, see the document: axios - Cancellation

  • signal
  • cancelToken(deprecated since v0.22.0)

Since the axios version I use is lower than v0.22.0, the latter is used here for encapsulation

// ...

// request interceptor
service.interceptors.request.use(
  config => {
    
    
    // ...

    // set cancel token
    if (config.useCancelToken) {
    
    
      const CancelToken = axios.CancelToken
      config.cancelToken = new CancelToken(cancel => {
    
    
        config.useCancelToken.value = cancel
      })
    }
    return config
  },
  error => {
    
    
    return Promise.reject(error)
  }
)

Detect whether useCancelTokenthe attribute , and add cancelTokenthe attribute method to the configuration if it exists, and point the value useCancelTokenof cancel.
use:

const cancelToken = ref()
function getAllData() {
    
    
  axios.get('/demo', {
    
     useCancelToken: cancelToken })
}

onBeforeUnmount(() => {
    
    
  cancelToken.value?.()
})

Enable axios Cancellation through useCancelTokenthe attribute , and interrupt the request in the current component before the component is destroyed.
The interrupt encapsulation of axios ends here.

The use of interrupt function encapsulation

In the above example, it can be seen that using the interrupt function is somewhat cumbersome. For each interface in the component that needs to use the interrupt function, you need:

  • Define a reactive interrupt method variable
  • Add to request configuration
  • Add onBeforeUnmountthe method , and call each of the previous interrupt methods inside it

Here is an axios interrupt function using hooks based on personal style, for reference only:

import {
    
     ref, onBeforeUnmount } from 'vue'

/**
 * @description: 自动取消axios请求
 * @example
 * // import:
 * import autoCancelAxios from '@/use/auto-cancel-axios'
 *
 * const { addCancelToken } = autoCancelAxios()
 * // define cancelToken(s):
 * const cancelToken = ref()
 * function getAllData() {
 *   axiosGet('/demo', { useCancelToken: addCancelToken() })
 * }
 */
export default () => {
    
    
  // cancelToken 列表
  const cancels = []
	// 添加 cancelToken
  function addCancelToken() {
    
    
    const currAxiosCancelToken = ref()
    cancels.push(currAxiosCancelToken)
    return currAxiosCancelToken
  }

  onBeforeUnmount(() => {
    
    
    try {
    
    
      cancels.forEach(cancelToken => {
    
    
        cancelToken.value?.()
      })
    } catch (error) {
    
    
      console.error('Failed to cancel axios', error)
    }
  })

  return {
    
     addCancelToken }
}

Example usage:

import autoCancelAxios from '@/use/auto-cancel-axios'

const {
    
     addCancelToken } = autoCancelAxios()

function getAllData() {
    
    
  axios.get('/demo', {
    
     useCancelToken: addCancelToken() })
}
function getData1() {
    
    
  axios.get('/demo1', {
    
     useCancelToken: addCancelToken() })
}
function getData2() {
    
    
  axios.get('/demo2', {
    
     useCancelToken: addCancelToken() })
}

Summarize

The purpose of encapsulation is to facilitate your own use, reduce code redundancy, facilitate maintenance, and improve development efficiency, so there is no standard answer.
This article is for reference only, please correct me if there is any mistake!

おすすめ

転載: blog.csdn.net/ymzhaobth/article/details/130930243