1. Introduction
The most commonly used HTTP library for Web development is Axios, whose underlying layer is wrapped around Ajax and is available on both the browser and server side. If you are not familiar with some apis, please refer to the official address, or the Chinese community. There are a lot of Axios encapsulated articles on the web, but that doesn’t stop me from taking a beating from the community leaders.
2. Involved service scenarios
- Multiple environments: development, test, and production environments.
- Unified error handling: 401, 404, 500, etc.
- Network disconnection and request timeout processing.
- Request cancellation/request interception: Prevents repeated requests from being sent to the server and causing server stress.
- Request permission: Some interfaces must be logged in to be accessible.
Of course, these scenarios are not the only ones involved. You need to configure them based on the service scenario.
3. How to encapsulate
Here, for convenience, I use the Class feature in ES6 to abstract an HttpRequest “Class” (JS has no real classes) with the following attributes:
baseURL
: Indicates the root path of the current requestpending
: an object that is stored as a key using the URL of the request APIcancelToken
Methods are:
getDefaultConfig
: Stores default configurationscreateAxiosInstance
: Creates an Axios instanceinterceptors
: Configure interceptorscancelKeyManager
: cancelKey managerhandleRequestCancel
: Handles request interception and request cancellationremoveRequest
: Remove requestmergeOptions
: utility functions that merge objectsget
: get request methodpost
: Post request method
Let’s start wrapping
3.1 Unified baseURL management
There are many ways to set baseURL, such as defining NODE_ENV in process.env, setting global variables at runtime using webpack.DefinePlugin (this part is already built into the Vue CLI), You can also use the. Env.[mode] file in Vue (see here for details).
I chose the former option and created a config folder in the SRC directory to store the configuration needed for the project to run (the compile-time configuration is configured in vue.config.js).
src/config/index.js
export default {
baseURL: {
dev: 'http://localhost:3000'.test: 'http://ip1:port1'.prod: 'http://ip2:port2'}}Copy the code
We only need to apply the unused variables through process.env.node_env.
src/utils/axios.js
import config from '@/config'
import HttpRequest from './HttpRequest'
// Get the API root directory based on the current environment
const baseURL = process.env.NODE_ENV === 'production' ? config.baseURL.prod : config.baseURL.dev
// Create an HtpRequest object instance
const axios = new HttpRequest(baseURL)
export default axios
Copy the code
HttpRequest above is a wrapper around AXIOS, so let’s complete the HttpRequest “class”.
3.2 Defining data attributes
class HttpRequest {
// * Set the default value to null to facilitate the use of the devServer agent
constructor (baseURL = ' ') {
this.baseURL = baseURL
this.pending = {} // Stores the cancel function for each request}}Copy the code
Above I gave baseURL a default empty string, because we may use the proxy during development. If baseURL is not empty, the request path set by the proxy will be invalidated.
3.2 Setting Default Settings
/** * axios default configuration **@return {Object} Axios default configuration *@memberof HttpRequest* /
getDefaultConfig () {
return {
baseURL: this.baseURL, // API root path
headers: {
// Each request carries a token
common: {
Authorization: `Bearer ${store.state.token}` || ' '
},
post: {
'Content-Type': 'application/json; charset=utf-8'}},timeout: 10 * 1000 // Request timeout :10 seconds after the request fails}}Copy the code
Here is the Axios default request configuration. I have configured the common configuration of the project. Here I talk about Authorization. I use JWT authentication mechanism here, so I need to carry token. Token is stored in VUEX and localStorage, so VUex needs to be introduced.
import store from '@/store'
Copy the code
I also set the default entity request header for POST to Application/JSON, encoding UTF-8, which is the most common data format we use in Web development. Of course, if you submit a form type, you’ll need to change the entity request header to ‘multipart/form-data’ or ‘Application/X-www-form-urlencoded’, At the same time, the former needs to submit a FormData object (data:new FormData) when the request, and the latter can introduce qs library to convert the object into this format: key1= 1ValuE1 & KEY2 =value2.
3.3 Creating an Axios Instance
With that in mind, we need to create an Axios instance using Axios’s Create method, pass in the configuration, and then configure the interceptor.
/** ** Create an Axios instance *@param {Object} Options Configuration * uploaded by the user@return {Axios} Return axios instance *@memberof HttpRequest* /
createAxiosInstance (options) {
const axiosInstance = axios.create()
// Merge the default and user-set configurations
const newOptions = this.mergeOptions(this.getDefaultConfig(), options)
// Call the interceptor
this.interceptors(axiosInstance)
// Return the instance
return axiosInstance(newOptions)
}
Copy the code
In the createAxiosInstance method, I basically create an instance, merge the default configuration with the user-passed configuration, and then invoke the interceptor to configure request and response interception. The mergeOptions method is used to merge two objects:
/** * Merge configuration **@param {Object} Default The configuration * has been configured@param {Object} Source Incoming configuration *@return {Object} Return new configuration *@memberof HttpRequest* /
mergeOptions (default, source) {
if (typeofsource ! = ='object' || source == null) {
return default
}
return Object.assign(default, source)
}
Copy the code
3.4 Configuring interceptors
The interceptor is mainly used before a request or response. For example, it can determine whether a request is repeated before a request, and it can add formatting parameters uniformly to all requests. I think Axios library is very powerful. In interceptors I mainly do one thing: cancel or block repeated requests.
We can imagine a scenario:
A home page list with different categories and page numbers that users can click on to filter and display different data, or click on a page to skip to the next page. If the user classification or frequent switching frequently said with a short time on the following page, then launched a lot of get requests at this time, though they different parameters, but in fact the same interface, request to the server at this time of the may bring pressure to the server (assuming the server support concurrency co., LTD.), and under the condition of the network is bad, If the later request responds before the earlier one, the data that was sent later should be shown to be overwritten by the earlier one. Let’s assume the request looks like this:
/list? catalog=front&page=0/list? catalog=backend&page=1/list? catalog=backend&page=2.Copy the code
In this case, what we want to do is to switch categories and if the previous request is cancelled without a response, this is called request cancellation.
Let’s look at another scenario:
Assume that the user fill out the form, web page hasn’t been shows “submit success” prompt, he thought that didn’t submit the past was crazy click submit (maybe bad network), the result is launched a large number of repeat request (parameters), assuming the server didn’t handle, the database in a short period of time will be a lot of duplicate data. Let’s assume that the request (POST) looks like this:
/submit
// Payload: application/ Json format
{
phone: 13312432576.isHandSome: true.city: 'Beijing'
}
Copy the code
In this case, we need to intercept the sent request and wait for the first request to be processed before sending the next request. This is called request interception.
We can handle both cases using the CancelToken function provided by Axios in conjunction with the interceptor. This function receives an Executor executor argument, which is defined internally, and an Executor argument, which in turn receives a Cancel argument, which we can use to intercept or cancel the request. Here’s how it’s used (it’s not created with source because it’s possible to create your own cancel function for each request) :
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// The executor function takes a cancel function as an argumentcancel = c; })});// cancel the request
cancel();
Copy the code
Returning to our requirements, we can define a pending object in the HttpRequest “class” to store each pending request, using the URL of the request as the key and the value as the cancel function. We need to set different keys for both request cancellation and request interception. Here I define a cancelKeyManager method to manage different types of keys:
/** * cancelKey manager **@return {Object} Returns an object that exposes two methods, one to obtain the key of the request, and one to set the key * of the request@memberof HttpRequest* /
cancelKeyManager () {
const expose = {}
expose.setKey = function setKey (config, isUniqueUrl = false) {
const { method, url, params, data } = config
expose.key = `${method}&${url}`
// It is mainly aimed at the situation that users frequently switch categories and request the next page to intercept the already sent requests
if(! isUniqueUrl) {return
}
// For the same request, such as request verification code, submit form, mainly used to cancel the current request, wait for the completion of the last request to send
expose.key = method === 'get' ? `${expose.key}&${qs.stringify(params)}` : `${expose.key}&${qs.stringify(data)}`
}
expose.getKey = function getKey () {
return expose.key
}
return expose
}
Copy the code
Again: Request Cancellation cancels the same request with different parameters, whereas request interception intercepts the exact same request. In the setKey method above, I use isUniqueUrl to determine whether the current request is “request cancel” or “request block.”
With the key in place, we need to decide whether to intercept or cancel the request based on the circumstances.
For “request cancellation,” the logic now is if the request key already existspending
, so we need to removependig
Set the key and value of the request, and clear the key and value after the request response.
For request interception, the logic now is if the request key already existspending
, then we need to call the cancel function of this request to intercept this request, and clear the key and value after the request response.
Here is the interceptor code:
/** * interceptor **@param {Axios} instance
* @memberof HttpRequest* /
interceptors (instance) {
// Add request interceptor
instance.interceptors.request.use((config) = > {
// Intercepts the request
// If the POST request format is Application /x-www-form-urlencoded, serialize data
if (
config.headers.post['Content-Type'].startsWith('application/x-www-form-urlencoded') &&
config.method === 'post'
) {
config.data = qs.stringify(config.data)
}
// Handle request interception and request cancellation. By default, request cancellation is used to cancel a request that has already been sent
// Pass false as the second parameter to intercept the request and wait for the response of the previous request before sending it
config = this.handleRequestCancel(config)
// Return formatted JSON as required by the background
config.url = `${config.url}? pretty`
return config
}, (error) = > {
// What to do about the request error
errorHandle(error)
return Promise.reject(error)
})
// Response interceptor
instance.interceptors.response.use((res) = > {
// Get the key of this request
const manager = this.cancelKeyManager()
const key = manager.getKey()
// Clear pending keys to indicate that the request has been answered
this.removeRequest(key, false)
// Axios responds properly
if (res.status === 200) {
return Promise.resolve(res.data)
}
return Promise.reject(res)
}, (error) = > {
// Do something about the response error
errorHandle(error)
return Promise.reject(error)
})
}
Copy the code
Note that in the request interceptor I also do two things to the request: if the Content-Type in the post header is Application/X-www-form-urlencoded, then serialize the parameters; Add the pretty parameter to all requests so that the data returned is formatted JSON data.
Set key and cancelToken for request Block and request cancel:
/** * handles request interception and request cancellation@param {object} Config axios Configuration object *@param {boolean} [isCancel=true] identifies whether to request cancellation or intercept the request *@return {object} Returns the AXIOS configuration object *@memberof HttpRequest* /
handleRequestCancel (config, isCancel = true) {
// Set the request key
const { setKey, getKey } = this.cancelKeyManager()
setKey(config, true)
const key = getKey()
const CancelToken = axios.CancelToken
// Cancel the request already sent
if (isCancel) {
this.removeRequest(key, true)
// Set cancelToken for this request
config.cancelToken = new CancelToken(c= > {
this.pending[key] = c
})
} else {
// Intercept this request
config.cancelToken = new CancelToken(c= > {
// Pass in the cancel function for this time
this.removeRequest(key, true, c)
})
}
return config
}
Copy the code
Action to remove request:
/** * Remove request **@param {string} Key Identifies the key * of the request@param {boolean} [isRequest=false] identifies whether the current function is requesting or responding to an interceptor call@param {function} C Cancel function *@memberof HttpRequest* /
removeRequest (key, isRequest = false, c) {
Check whether the current request is pending before making a request. If so, there are two cases:
// 1. If the previous request is not responded and the current request is considered to be a repeated request, the cancel method is invoked to intercept the repeated request or cancel the previous request
// 2. The last request has been responded, and is called in response, clearing key
if (this.pending[key]) {
if (isRequest) {
const msg = 'Your operation is too frequent, please try again later'
c ? c(msg) : this.pending[key](msg)
} else {
// The last request called the cancel function to remove the key after a successful response
delete this.pending[key]
}
}
}
Copy the code
3.6 Unified error handling
Here we are already configured interceptor finished, there is also a unified error handling operation, unified error handling is divided into two kinds of circumstances, the request and response, but there will be 4, 5 xx xx HTTP status code error, another is broken network, such as the request timeout request not been sent out, let’s look at how to deal with the error:
src/utils/errorHandle.js
import MessageBox from '@/components/messageBox/src/local'
/** * AXIos unified error handling is for HTTP status code errors *@param {Object} err* /
function errorHandle (err) {
// Determine whether the server responded
if (err.response) {
switch (err.response.status) {
// The user has no permission to access the interface
case 401:
MessageBox.$alert('Please log in ~')
break
// The requested resource does not exist
case 404:
/ / 404
MessageBox.$alert('The requested resource does not exist.')
break
// Server 500 error
case 500:
MessageBox.$alert('Server exception, please try again later.')
break}}else if (
err.code === 'ECONNABORTED' ||
err.message === 'Network Error' ||
err.message.includes('timeout') ||
!window.navigator.onLine
) {
// Handle timeouts and disconnections
MessageBox.$alert('Network disconnected, please check your network connection ~')}else {
// Perform other processing
console.log(err.stack)
}
}
export default errorHandle
Copy the code
MessageBox above is my custom play component, prompt to the user’s message, you can also use the UI framework component. Finally, there is a request response, and the HTTP status code is 2xx request, this is usually a parameter error or other client error, so we need to negotiate with the backend field, and then processing.
3.7 Configuring Requests (GET and POST)
With the previous configuration in place, LET’s apply the above configuration to our usual GET and POST request methods:
/** * get encapsulates **@param {String} Url Request address *@param {Object} Query Request parameter *@param {Object} Config Request configuration *@return {Promise} Return a Promise *@memberof HttpRequest* /
get (url, query, config = {}) {
return this.createAxiosInstance({
url,
method: 'get'.params: query, ... config }) }/** * the post method encapsulates **@param {String} Url Request address *@param {Object} Data Requests body data *@param {Object} Config Request configuration *@return {Promise} Return a Promise *@memberof HttpRequest* /
post (url, data, config = {}) {
return this.createAxiosInstance({
url,
method: 'post', data, ... config }) } }Copy the code
The other request methods are similar and can be written in the format above.
3.8 Unified API Management
Create an API directory under SRC to manage the API, then manage the API by module, and expose the API via index.js:
src/api/login.js
import axios from '@/utils/axios'
/ / register
const reg = (data) = > axios.post('/api/reg', data)
export default {
reg
}
Copy the code
src/api/content.js
import axios from '@/utils/axios'
// Get the list
const getList = (query, config) = > axios.get('/api/list', query, config)
export default {
getList
}
Copy the code
src/api/index.js
import login from './login'
import content from './content'
export default {
login,
content
}
Copy the code
Finally, we mount this object to the Vue prototype and call it directly from this in other components later, which is convenient:
import api from '@/api' // Import the global interface
Vue.prototype.$api = api
Copy the code
Here’s a pretty crude Axios wrapper
4. To summarize
This article mainly starts from some basic business, the secondary encapsulation of Axios, so that we can better manage the project, spend more time on business development, save more time, hope my humble opinion can help you. The Axios library is really powerful. So far, I have only read the source code of CancelToken. I will read the source code thoroughly in the future to learn its code design.