current position:Home>How to extend the functionality of Axios without interceptors

How to extend the functionality of Axios without interceptors

2022-04-29 16:27:51iel

background

In my daily work , All of them are based on axios As a tool for requesting back-end interface data in front-end applications , Its advantages will not be described in this paper , Here's how to make it easy to use axios And how to make it function Replicable Scalable Easy to transplant Carries on the discussion .

The problem with interceptors

axios The interceptor function of must be familiar to everyone , It is mainly used for additional processing of configuration items or data status after request and response , But this feature will not be used as a topic in this article , But here's why .

  • Expand the high coupling between different functions , Not easy to disassemble and maintain
  • The extended function is not easy to transplant
  • The more functions, the harder it is to expand

Function of sample

Cancel duplicate request function

export const http = axios.create({ baseURL: '/api' })

//  General practice of eliminating duplicate request function 

//  Use  map  Record interface data 
export const pendingServices = new Map()

/** *  Generate interface tags  * * @param {import('axios').AxiosRequestConfig} config  Request configuration item  */
function createMark(config = {}) {
  const { method = 'get', url = '', data = {}, params = {} } = config

  //  After the request, the configuration item will be converted to a string , In order to ensure that the request is consistent with the response, additional processing is performed here 
  const processValue = (value) =>
    typeof value === 'string' ? safeJsonParse(value, value) : JSON.parse(JSON.stringify(value))

  //  Serialization parameters generate unique identifiers 
  return serialize({
    method,
    url,
    data: processValue(data),
    params: processValue(params)
  })
}

/** *  Interface cancellation type  */
export const ServiceCancelTypeEnum = {
  /** *  Time out cancellation  */
  timeout: 'timeout',
  /** *  Manually cancel  */
  manual: 'manual'
}

/** *  Whether to cancel the request manually  * * @param {string} cancelType  Interface request cancellation type  * * @returns {boolean} */
export function isManualCancel(cancelType) {
  return cancelType === ServiceCancelTypeEnum.manual
}

/** *  Add interface request  * * @description  Avoid repeated interface requests with throttling thinking . * * @param {import('axios').AxiosRequestConfig} config  Interface configuration item  * * @example * ```js * http.get('/api') // A * http.get('/api') // B * * // B  Be cancelled  * // pendingServices  There is only  A  Information about  * ``` */
function addPendingService(config = {}) {
  //  If the request is allowed to repeat or has carried  cancelToken  Then return directly 
  if (!config.$cancelRepeatRequest || !!config.cancelToken) return

  //  Generate interface identification 
  const mark = createMark(config)

  //  Judge whether the current ID already exists 
  if (pendingServices.has(mark)) {
    //  Cancel this call when the request is repeated 
    config.cancelToken = new Axios.CancelToken((cancel) => {
      //  Record the cancellation type of the current interface 
      config.$cancelType = ServiceCancelTypeEnum.manual
      //  The current interface of the record is cancelled due to repeated requests 
      config.$isRepeatCancel = true

      // cancel  The data passed in by the function will be used as the response after failure  error.message
      cancel(config)
    })
  } else {
    //  because  CancelToken  The callback is asynchronous , So keep records here first 
    pendingServices.set(mark, { mark, cancel: null, config })
    config.cancelToken = new Axios.CancelToken((cancel) => {
      pendingServices.get(mark).cancel = cancel
    })
  }
}

/** *  remove  pendingServices  Interface request in  * * @param {import('axios').AxiosRequestConfig} config  Interface configuration item  */
function removePendingService(config = {}) {
  //  If it is a duplicate request, it will directly return 
  if (config.$isRepeatCancel) return

  const mark = createMark(config)

  if (!pendingServices.has(mark)) return null

  const service = pendingServices.get(mark)

  pendingServices.delete(mark)

  return service
}

/** *  Get the corresponding request configuration item in error response  * * @param {any} error  Wrong data  * * @returns {import('axios').AxiosRequestConfig} */
function getErrorConfig(error) {
  if (error.config) return error.config

  return typeof error.message === 'object' ? error.message : null
}

//  Request to intercept 
http.interceptors.request.use((config) => {
  //  Cancel duplicate request , Default  true
  config.$cancelRepeatRequest ??= true
  //  Cancel type , Used to distinguish between duplicate cancellation and timeout cancellation 
  config.$cancelType = undefined
  //  Whether it is a repeated cancellation request 
  config.$isRepeatCancel = false
  
  //  Add interface record 
  addPendingService(config)

  return config
})

//  The response to intercept 
http.interceptors.response.use(
  (response) => {
    //  Remove interface record 
    removePendingService(response.config)

    return response.data
  },
  (error) => {
    //  Get the request configuration item in the error message 
    const config = getErrorConfig(error)
    //  Determine whether it is a cancellation request , Interface timeout  isCancel  by  false , So judgment is needed  message
    const isCancel = Axios.isCancel(error) || error.message?.indexOf('timeout') > -1
    //  Get cancellation type 
    const cancelType = isCancel ? getCancelType(config) : ''

    //  Remove the request record according to the configuration item 
    removePendingService(config)
    
    if (isManualCancel(cancelType)) {
      //  Here is just a simple explanation for the repetition and cancellation , If there is a response package, it can be handled by itself 
      return Promise.resolve({ data: { isManualCancel: true } })
    }
    
    //  Throw an error up 
    return Promise.reject(new Error(error))
  }
)
 Copy code 

vue Example used in :

export default {
  methods: {
    async loadData() {
      try {
        //  The default is to cancel duplicate requests 
        const response = http.get('/demo/list')
        
        //  If it is repeated cancellation or the component has been uninstalled, terminate the subsequent operation 
        if (response.data.isManualCancel || this._isDestroyed) return
        
        // do somethings...
      } catch(err) {
        console.log(err.message)
      }
    }
  }
}
 Copy code 

The above is the encapsulation of repeated requests for interfaces , It is a relatively common function , But we still need to complete the whole request process , Interceptor code is executed from the request to the end of the response , The requested operation cannot be terminated directly when it is judged to be repeated .

This situation will cause us to expand other functions of the interceptor , Some additional logic needs to be added when the repeated request is cancelled to ensure the normal execution of the process , But this increases the coupling between different functions .

Discussion on Problems

The above questions may be said by friends :“ I can split the code of different functions into different files , It's still very easy to maintain .”

The author has also tried this scheme , But different functions are performed at different times , And because in the project, in order to avoid try...catch Logic , The interface response is usually repackaged , Through different data structures to reflect success and failure , Make the code look smoother and more robust ( Missing the bottom of the mistake ), The benefits are obvious , But there are more points to consider when we need to expand other functions , So this scheme can only optimize http The code in the file is not too redundant .

The second is that different projects may need different functions , But even if the code with low coupling is migrated, it will still involve the change of source code , Therefore, the interceptor scheme is still not widely applicable .

Solutions

After thinking over and over again , Think the problem is caused by the interceptor , Then we still have to start with interceptors ,axios Since the built-in interceptor cannot meet the needs , So can we achieve our needs by simulating the interceptor function ?

Plan implementation

After exploration , combination vue Of hooks Use experience , Developed a plug-in axios-ext, By registering different plug-ins axios Realize different functions .

Examples of use

import { createAxios } from '@iel/axios-ext'
import AxiosExtCancelRepeat from '@iel/axios-ext-cancel-repeat'
import axios from 'axios'

//  receive  axios  Configure the item or instance and return the wrapped  axios  example 
// http.$axiosExt  by  AxiosExt  example 
const http = createAxios(axios)

//  Register the plug-in , By default, the internal functions of plug-in methods will be executed 
//  Return the instance , Registered plug-ins will not be re registered 
http.$axiosExt
  //  Register the de duplication request plug-in 
  .use(AxiosExtCancelRepeat, {
    manualCancelMessage: ' Manually cancel the interface ',
    onRepeat: () => [true, ' Cancel duplicate interface ']
  })
  //  Register response wrapper plugin 
  .use(AxiosExtResponseWrap, {
    wrapper: AxiosResponseTupleWrapper([ErrorAdaptor, SuccessAdaptor])
  })

//  Destroy instance , Handle some things when the plug-in is destroyed and clean up all the plug-in information 
// http.$axiosExt.destroy()
 Copy code 

Sample screenshot

const loadData = async () => {
  const response = await http.get('/demo/list')
  console.log(response) // response  Is the... In the figure below  returnValue
}
 Copy code 

image.png

The test page

Can be found in This page To test !

Series plug-ins

Default plug-ins

Installation steps and cumbersome installation items .

ending

The above is the author's current understanding of axios Ideas and solutions for expanding functions , Maybe limited by the ability, it may be slightly insufficient. Please forgive me , If you have any questions, you can leave a message in the comment area !

copyright notice
author[iel],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291627428845.html

Random recommended