Github address: github.com/Lstmxx/axio…
preface
Some time ago when I was doing optimization in the company, I saw that many mobile terminal projects and desktop projects I wrote before used the insensitive refresh token, so I simply packaged this into an NPM package to use.
Refresh token without feeling
The refreshing token principle is quite simple, in fact, when the interface returns 401, apply for a new token at the back end.
Implementation approach
Check if 401 is returned in the Rejected response interceptor of AXIos. If so, refresh the token and send the request again.
- The encapsulation token is saved to the sessionStorage.
// ./src/util/tokenHelp.js
const TOKEN_KEY = 'token'
const storage = window.sessionStorage
export function setToken (token, type) {
storage.setItem(TOKEN_KEY, token)
storage.setItem('type', type)
}
export function getToken () {
const token = storage.getItem(TOKEN_KEY)
if (token) return token
else return false
}
export function getType () {
const type = storage.getItem('type')
return type
}
Copy the code
- Encapsulation axios
// /src/core/axios.js
import axios from 'axios'
import { merge } from '.. /util/util'
const baseConfig = {
baseUrl: ' '.timeout: 6000.responseType: 'application/json'.withCredentials: true.headers: {
get: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
},
post: {
'Content-Type': 'application/json; charset=utf-8'}},transformRequest: [function (data) {
data = JSON.stringify(data)
return data
}],
transformResponse: [function (data) {
// Perform any conversion on data
if (typeof data === 'string' && data.startsWith('{')) {
data = JSON.parse(data)
}
return data
}]
}
export function initAxios (config) {
const _config = merge(baseConfig, config)
const _axios = axios.create(_config)
return _axios
}
Copy the code
The merge function is written in reference to axios’s merge, primarily to merge config. Of course, object. assign is not impossible, but after all, the purpose is to package NPM, compatibility should be checked.
- The wrapper throws the Service object. Notice here that I have added async to the error receiving function of response. With await, I can write synchronous code without embedding both then and catch.
// ./src/index.js
import { setToken, getToken } from './util/tokenHelp'
import { isFunction } from './util/util'
import { initAxios } from './core/axios'
const Service = function Service (options) {
this.isReflash = false
this.reTryReqeustList = []
const config = options.config ? options.config : {}
const responseInterceptors = options.responseInterceptors
const requestInterceptors = options.requestInterceptors
const reflashTokenConfig = options.refleshTokenConfig || null
const getTokenFn = options.getTokenFn
const axios = initAxios(config)
function _refleshToken () {
return new Promise((resolve, reject) = > {
axios.request(reflashTokenConfig).then((response) = > {
const isResolve = getTokenFn(response, setToken)
if (isResolve) resolve()
else reject(new Error('get token error'))
}).catch((err) = > {
reject(err)
})
})
}
axios.interceptors.request.use((_config) = > {
const c = isFunction(requestInterceptors) ? requestInterceptors(_config, getToken) : _config
return c
}, (error) = > {
error.data = {}
error.data.msg = 'Server exception, please contact administrator! '
return Promise.resolve(error)
})
axios.interceptors.response.use((response) = > {
if (isFunction(responseInterceptors)) {
return responseInterceptors(response)
}
return response
}, async (error) => {
if (error.response && error.response.status === 401) {
try {
refleshTokenConfig && await _refleshToken()
return axios.request(error.response.config)
} catch (err) {
return Promise.reject(err)
}
}
return Promise.reject(error)
})
this.axios = axios
}
Service.prototype.request = function request (options) {
return this.axios.request(options)
}
export default Service
Copy the code
After the package is complete, introduce to the business layer call test to see first ~
import Service from './axios-reflash-token/src/index.js'
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
case 1001:
message = 'Verification failed'
break
default:
message = 'Connection error (${status})! `
}
return `${message}Please check the network or contact the administrator! `
}
const options = {
config: {
baseURL: 'http://test.platform.xbei.pro/api'.timeout: 6000
},
requestInterceptors: function (config, getToken) {
config.headers.Authorization = 'Bearer ' + getToken()
return config
},
responseInterceptors: function (response) {
const data = response.data
const res = {
status: data.code || response.status,
data,
msg: ' '
}
res.msg = data.message || showStatus(res.status)
return res
},
refreshTokenConfig: {
url: '/platform/login'.method: 'POST'.data: {}},getTokenFn: function (response, setToken) {
if (response.data.code === 200) {
setToken(response.data.data.token, response.data.data.tokenType)
return true
}
return false}}const service = new Service(options)
...
Copy the code
And as expected, it was done… Wait, what happens when you have multiple requests at the same time
Oh, this is not ok. I switched token acquisition several times. This is because in the case of multiple requests, each request is asynchronous. Before the token is set, reflashToken will be invoked several times to refresh the token.
The multiple token refresh problem is resolved
Actually solution is also very simple, because axios. Interceptors. Whether it is fulfilled in the response or is rejected is returned Promise (axios request inside chain calls), However, after the Promise enters pending, it will wait for the resolve or reject until it reaches the depressing or Rejected. Therefore, as long as we save the corresponding resolve with the array when we return the Promise, Then wait until the token is refreshed and then execute the requests in the array one by one.
- To improve the
const Service = function Service (options) {
this.isReflesh = false
this.reTryReqeustList = []
...
axios.interceptors.response.use((response) = > {
if (isFunction(responseInterceptors)) {
return responseInterceptors(response)
}
return response
}, async (error) => {
if (error.response && error.response.status === 401) {
if (!this.isReflash) {
this.isReflash = true
try {
reflashTokenConfig && await _reflashToken()
this.isReflash = false
while (this.reTryReqeustList.length > 0) {
const cb = this.reTryReqeustList.shift()
cb()
}
return axios.request(error.response.config)
} catch (err) {
return Promise.reject(err)
}
} else {
return new Promise((resolve) = > {
this.reTryReqeustList.push(
() = > resolve(axios.request(error.response.config))
)
})
}
}
return Promise.reject(error)
})
...
}
Copy the code
Let’s test it out again
OK! No problem, then the next step is to pack into an NPM package.
packaging
The package is initialized using rollup
mkdir axios-refresh-token
cd axios-refresh-token
npm init
Copy the code
Modify the package. The json
"main": "dist/axios-refresh-token.cjs.js", "module": "dist/axios-refresh-token.esm.js", "browser": "dist/axios-refresh-token.umd.js", ... "Dependencies" : {" axios ":" ^ 0.21.0 "}, "devDependencies" : {" @ Babel/core ": "^ 7.0.0 @", "Babel/plugin - transform - runtime" : "^ 7.12.1", "@ Babel/preset - env" : "^ 7.12.1", "@ rollup/plugin - Babel" : "^ 5.2.1," "@ rollup/plugin - commonjs" : "^ 16.0.0", "@ rollup/plugin - json" : "^ 4.1.0", "@ rollup/plugin - node - resolve" : "^ 10.0.0," "@ types/babel__core" : "^ 7.1.9", "a rollup" : "^ 2.0.0", "a rollup - plugin - terser" : "^ 7.0.2"}, "files" : [ "dist", "src" ] ...Copy the code
After modification, NPM install installs dependencies. This package uses five plug-ins.
- Rollup /plugin-json Converts JSON files to ES6 modules
- Rollup /plugin-node-resolve resolves node_modules files
- Rollup /plugin-commonjs Converts commonJS modules to ES6 modules for rollup to handle
- @ rollup/plugin – Babel integrated Babel
- Rollup-plugin-terser compression code
Create a new rollup.cofnig.js file in the project root directory.
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser'
// const isDev = process.env.NODE_ENV ! == 'production'
export default {
input: 'src/index.js'.output: [{file: 'dist/axios-reflash-token.cjs.js'.format: 'cjs'.name: 'cjs'
},
{
file: 'dist/axios-reflash-token.es.js'.format: 'es'.name: 'es'
},
{
file: 'dist/axios-reflash-token.umd.js'.format: 'umd'.name: 'umd'}].plugins: [
json(),
nodeResolve(),
commonjs(),
babel({
babelHelpers: 'runtime'.exclude: 'node_modules/**'.plugins: [
'@babel/plugin-transform-runtime'
]
}),
terser()
]
}
Copy the code
Packaging –
rollup -c ./rollup.config.js
Copy the code
If you want to package to NPM, you also need to use the NPM adduser command to add users. After that, you can upload it.
NPM publish --access public // Publish --access public if @xxx/ is includedCopy the code
Once packed, you can install the introduction
NPM install @lstmxx/axios-refresh-token --save import Service from '@lstmxx/axios-refresh-token'Copy the code
Refer to the connection
- [1] developer.mozilla.org/zh-CN/docs/…
- [2] www.axios-js.com/
- [3] rollupjs.org/guide/en/#i…