Axios is an official HTTP library recommended by Vue. It is described in the axios official introduction, which is:

Axios is a Promise-based HTTP library that can be used in browsers and Node.js.

As an excellent HTTP library, Axios was strongly recommended by Vue author You Xiaoyou as the best choice for HTTP library in Vue project, beating vue-Resource which was maintained by Vue official team.

While AXIos is an excellent HTTP library, it’s not that convenient to use directly in a project, so we need to encapsulate it to some degree to reduce repetitive code and make it easier to call. Let’s talk about the encapsulation of AXIos in Vue.

start

There is a lot of code around axios encapsulation, but most of it is configured in the entry file (main.js) in the form of axios global object property definitions, similar to the following code:

axios.defaults.timeout = 10000
Copy the code

This scheme has two disadvantages. First, AXIos encapsulates the code coupled to the entry file, which is not convenient for later maintenance. Second, the code is too fragmented to configure the way AXIOS global object attributes are defined.

For the first problem, I used a core idea in the Vue source structure — to split the function into files, convenient for later maintenance. Create a separate http.js or http.ts file, import axios into the file, package it with configuration, and finally export and mount it to the prototype of Vue. In this case, every time you change the AXIOS configuration, you only need to modify the corresponding file, and irrelevant functions will not be affected.

For problem 2, configuration encapsulation is implemented by creating an AXIos instance through configuration items recommended by AXIos.

The code is as follows:

// http.js
import axios from 'axios'
// Create an axios instance
const service = axios.create({
  / / configuration items
})
Copy the code

Set baseURL according to the environment

The baseURL attribute is the request address prefix and is automatically prefixed to the URL unless the URL is an absolute address. Normally, there are different BaseurLs in the development environment and production mode, so we need to switch different BaseurLs for different environments.

In development mode, due to the presence of devServer, the request address needs to be rewritten based on the fixed URL prefix, so in development, set baseURL to a fixed value, such as: /apis.

In production mode, different baseURL can be set depending on the request prefix of the Java module.

The specific code is as follows:

// Use process.env.node_env to differentiate between different baseURL states
const service = axios.create({
	baseURL: process.env.NODE_ENV === 'production' ? `/java` : '/apis',})Copy the code

Set the request header uniformly

Here we talk about a question, what is encapsulation? In my opinion, encapsulation is about covering more calling scenarios with less calling code.

Since, for the most part, request headers are fixed, and only a few cases require some special headers, my approach here is to use a generic request header as the base configuration. When a special request header is needed, it is passed in as a parameter, overwriting the underlying configuration.

The code is as follows:

const service = axios.create({
    ...
	headers: {
        get: {
          'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
          In development, common request headers for single sign-on or other functions are also required, which can be configured as well
        },
        post: {
          'Content-Type': 'application/json; charset=utf-8'
          In development, common request headers for single sign-on or other functions are also required, which can be configured as well}}})Copy the code

Cross-domain, timeout, response code processing

In AXIos, you can easily handle cross-domain and time-out problems by providing the withCredentials attribute, which allows cross-domain access, and the timeout attribute, which allows configuring timeout times.

Now, let’s talk about response code processing:

Axios provides the validateStatus attribute, which defines a resolve or Reject PROMISE for a given HTTP response status code. Therefore, under normal Settings, we set all requests with a status code of series 2 or 304 to Resolve and reject. As a result, we can use catch to uniformly catch error-responding requests in business code for unified processing.

However, since I am using async-await in my code, and it is known that async-await is extremely troublesome to catch, I choose to set all responses to resolve state and process them in THEN.

This part of the code is as follows:

const service = axios.create({
	// Whether credentials are required for cross-domain requests
	withCredentials: true.// The request timed out 30s
	timeout: 30000.validateStatus: function () {
		// Use async-await to handle reject, so return resolve to handle exception in business code
		return true}})Copy the code

Request and response processing

Without Axios, each request or response needs to be serialized.

In AXIos, transformRequest allows you to modify the request data before sending it to the server. The transformResponse allows the response data to be modified before being passed to the THEN /catch.

With these two hooks, you can eliminate a lot of repetitive serialization code.

The code is as follows:

const service = axios.create({
    Serialize the request data before sending it to the server
    transformRequest: [function (data) {
        data = JSON.stringify(data)
        return data
    }],
    // Modify the response data before passing it to then/catch
    transformResponse: [function (data) {
        if (typeof data === 'string' && data.startsWith('{')) {
            data = JSON.parse(data)
        }
        return data
    }]
})
Copy the code

The interceptor

Interceptors, divided into request interceptors and response interceptors, intercept requests or responses before they are processed by then or catch, respectively.

As mentioned earlier, because of the difficulty of handling a catch in async-await, an error case is also handled as a resolve state. But this presents a problem, in the case of a request or response error, the result is no DATA protocol defined in the MSG field (message). Therefore, we need to manually generate a return data that conforms to the return format when errors occur.

Since there is no need in the business to do additional processing in the request interceptor, the request interceptor’s resolve state is simply returned.

The request interceptor code looks like this:

// Request interceptor
service.interceptors.request.use((config) = > {
	return config
}, (error) => {
	// Error thrown to business code
    error.data = {}
    error.data.msg = 'Server exception, please contact administrator! '
    return Promise.resolve(error)
})
Copy the code

Back to the response interceptor problem, in addition to the request or response error, there is another case where the body of the message returned does not conform to the protocol specification, and that is when the status code is not series 2 or 304. At this point, we still need to do the same thing — manually generate a return that conforms to the return format. However, there is a difference, we also need to generate different prompt messages according to different status codes, in order to facilitate the handling of online problems.

The response interceptor code looks like this:

// Generate different prompts according to different status codes
const showStatus = (status) = > {
    let message = ' '
    // This block of code can be optimized using the policy pattern
    switch (status) {
        case 400:
            message = 'Request error (400)'
            break
        case 401:
            message = 'Not authorized, please log back in (401)'
            break
        case 403:
            message = 'Access denied (403)'
            break
        case 404:
            message = 'Request error (404)'
            break
        case 408:
            message = 'Request timed out (408)'
            break
        case 500:
            message = 'Server error (500)'
            break
        case 501:
            message = 'Service Not realized (501)'
            break
        case 502:
            message = 'Network Error (502)'
            break
        case 503:
            message = 'Service unavailable (503)'
            break
        case 504:
            message = 'Network Timeout (504)'
            break
        case 505:
            message = 'HTTP version not supported (505)'
            break
        default:
            message = 'Connection error (${status})! `
    }
    return `${message}Please check the network or contact the administrator! `
}

// Response interceptor
service.interceptors.response.use((response) = > {
    const status = response.status
    let msg = ' '
    if (status < 200 || status >= 300) {
        // Handle HTTP errors and throw into the business code
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = { msg }
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) => {
    // Error thrown to business code
    error.data = {}
    error.data.msg = 'Request timed out or server exception, please check network or contact administrator! '
    return Promise.resolve(error)
})
Copy the code

Tips1: Tip, the above pile of switch-case code can be optimized using policy mode

Tips2: You can add some service-related requirements to the interceptor, such as loading and authentication

Support the TypeScript

Due to some time ago, I pushed TypeScript in the department and rewrote all JS files into TS files to satisfy my obsession. Since Axios has typescript-related support, you simply import the corresponding type and assign it.

The complete code

// http.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

const showStatus = (status: number) = > {
  let message = ' '
  switch (status) {
    case 400:
      message = 'Request error (400)'
      break
    case 401:
      message = 'Not authorized, please log back in (401)'
      break
    case 403:
      message = 'Access denied (403)'
      break
    case 404:
      message = 'Request error (404)'
      break
    case 408:
      message = 'Request timed out (408)'
      break
    case 500:
      message = 'Server error (500)'
      break
    case 501:
      message = 'Service Not realized (501)'
      break
    case 502:
      message = 'Network Error (502)'
      break
    case 503:
      message = 'Service unavailable (503)'
      break
    case 504:
      message = 'Network Timeout (504)'
      break
    case 505:
      message = 'HTTP version not supported (505)'
      break
    default:
      message = 'Connection error (${status})! `
  }
  return `${message}Please check the network or contact the administrator! `
}

const service = axios.create({
  / / alignment
  baseURL: process.env.NODE_ENV === 'production' ? ` / ` : '/apis',
  headers: {
    get: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
    },
    post: {
      'Content-Type': 'application/json; charset=utf-8'}},// Whether to control requests for cross-site access
  withCredentials: true,
  timeout: 30000,
  transformRequest: [(data) = > {
    data = JSON.stringify(data)
    return data
  }],
  validateStatus () {
    // Use async-await to handle reject, so return resolve to handle exception in business code
    return true
  },
  transformResponse: [(data) = > {
    if (typeof data === 'string' && data.startsWith('{')) {
      data = JSON.parse(data)
    }
    return data
  }]
})

// Request interceptor
service.interceptors.request.use((config: AxiosRequestConfig) = > {
    return config
}, (error) = > {
    // Error thrown to business code
    error.data = {}
    error.data.msg = 'Server exception, please contact administrator! '
    return Promise.resolve(error)
})

// Response interceptor
service.interceptors.response.use((response: AxiosResponse) = > {
    const status = response.status
    let msg = ' '
    if (status < 200 || status >= 300) {
        // Handle HTTP errors and throw into the business code
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = {msg}
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) = > {
    // Error thrown to business code
    error.data = {}
    error.data.msg = 'Request timed out or server exception, please check network or contact administrator! '
    return Promise.resolve(error)
})

export default service
Copy the code