接上文《用 NuxtJS 构建 SSR 商城 实战笔记——初始化项目》继续我们的搬砖之旅。有不足之处或是任何意见建议,欢迎各位大佬不吝斧正。本篇主要介绍基于 NuxtJS 构建的 vue 后端渲染(SSR)项目中关于 axios 的配置、取消重复请求的发送与开发环境下浏览器跨域请求的实现~
axios 配置
安装并使用 axios
在创建项目的时候已经选择了安装 axios,如果没有,可以进行手动安装:npm i @nuxtjs/axios
。 安装好的 axios 作为模块,需要在 nuxt.config.js 中进行配置,添加进 modules 数组:
modules: ['@nuxtjs/axios']
之后就能在服务端上下文环境中拿到 $axios
(引入的模块对象会以 $ 开头) 用以请求数据了。
配置 axios
在 plugins 目录下新建 axios.js 文件,在里面默认导出一个函数,函数的参数中可以拿到 nuxt 的上下文环境,我们可以从中解构出需要的几个参数:$axios
用于发送 ajax 请求,redirect
用于强制跳转页面, route
路由信息和 store
vuex 数据。
基础配置
主要是配置请求的基础路径 baseURL
和请求超时时间 timeout
:
$axios.defaults.baseURL = process.env.BASE_URL
$axios.defaults.timeout = 100000
项目一般会分成 4 种环境:dev(开发)、test(测试)、staging(预发布)、和 prod(正式上线),不同环境的 baseURL 也不同,所以不能写死,而是用 process.env.BASE_URL
。具体说明可参看《设置环境变量及定义 baseURL》。
请求拦截
不同于原生的 axios,nuxt 项目中使用的 @nuxtjs/axios 提供了些官方文档称为 'helpers' 的方法。我们可以直接使用 onRequest(config)
进行请求拦截的相关配置,比如让请求头携带 token:
$axios.onRequest(config => {
if (store.getters.token) {
config.headers.authorization = 'Bearer ' + store.getters.token
}
return config
})
响应拦截
响应拦截的 helpers 是 onResponse(response)
$axios.onResponse(response => {
// 做些业务需要的处理
const res = response.data
return res
})
错误处理
错误处理的 helpers 是 onError(err)
,这里提前引入了 element-ui 的 Message
组件用于将错误反馈给用户。
$axios.onError(error => {
console.log(error) // for debug
const code = parseInt(error.response && error.response.status)
if (code === 401) {
// 这里根据几个特定常见的状态码进行特殊处理
}
let message = ''
if (error.response && error.response.data && error.response.data.error) {
message = error.response.data.error.message
} else if (error.request) {
message = '请求发出但没得到响应'
} else {
message = error.message
}
Message({
message: message || '请求发生错误',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
})
需要注意的是错误的发生可能存在 3 种情况,不同情况返回的错误信息不同:
error.response
有值代表请求已发出并得到服务器响应,但状态码不是以 2 开头;error.request
有值代表请求发出但没得到响应- 剩下的情况则为建立请求本身失败
关于更多 http 请求这档子事儿,我之前写了篇文章专门介绍,感兴趣的可以移步《HTTP 协议详解》
在 nuxt.config.js 添加配置
plugins: ['@/plugins/axios.js']
这样 nuxt 在初始化主应用程序之前,就会去找到定义在 plugins
内的文件执行,那么我们对于 axios 添加的配置才会其效果。
axios 避免重复提交
为了避免由于用户手抖等原因造成的同一个请求在很短的时间内重复提交,除了节流防抖之外,还可以从取消请求这个角度,通过对 axios 进行拦截配置来实现。
我们要做的就是对于同一个请求,如果上一次请求还没结束,又发送了一次,则取消上一次请求。实现的原理是如果我们在一个 axios 请求的 config 里配置了 cancelToken
。
let cancel // 用于保存取消请求的函数
axios({
url: 'http://test.com',
cancelToken: new axios.CancelToken(c => { // c 是用于取消当前请求的函数
cancel = c
})
})
axios 会传给我们一个参数 c
,它是一个用于取消当前请求的函数,我们把它赋值给变量 cancel
,那么执行 cancel()
,就会取消掉当前请求。
于是我们可以设置一个数组 requestList
用于存放所有发出去的请求,在请求拦截中判断当前请求是否存在于数组 requestList
中,如果是,则取消发送;不存在,则添加进数组,等待请求成功返回后再将其从数组删除。如果请求遇到错误,就将请求数组清空。具体代码如下:
export default function ({ $axios, redirect, route, store }) {
// 请求列表(防重复提交)
const requestList = []
// 获取 CancelToken
const { CancelToken } = $axios
// 请求拦截
$axios.onRequest(config => {
// ...
// 防止重复提交(如果本次是重复操作,则取消,否则将该操作标记到 requestList 中)
config.cancelToken = new CancelToken(cancel => {
const requestFlag =
JSON.stringify(config.url) +
JSON.stringify(config.data) +
JSON.stringify(config.params) +
'&' +
config.method
if (requestList.includes(requestFlag)) {
// 请求标记已经存在,则取消本次请求,否则在请求列表中加入请求标记
cancel() // 取消本次请求
} else {
requestList.push(requestFlag)
}
})
})
// 响应拦截
$axios.onResponse(response => {
// ...
// 请求返回后,将请求标记从 requestList 中移除
const requestFlag =
JSON.stringify(response.config.url) +
JSON.stringify(response.config.data) +
JSON.stringify(response.config.params) +
'&' +
response.config.method
requestList.splice(
requestList.findIndex(item => item === requestFlag),
1
)
})
// 错误处理
$axios.onError(error => {
// ...
requestList.length = 0 // 置空请求列表
const code = parseInt(error.response && error.response.status)
// 如果不是被取消的而产生的错误
if (!$axios.isCancel(error)) {
Message({
message: message || '请求发生错误',
type: 'error',
duration: 5 * 1000
})
}
})
}
注意几点:
- 判断是否为同一个请求是根据请求的 url+ 请求参数 + 请求方式,组合成一个字符串,作为请求标记。注意请求参数应该包括
config.data
和config.params
分别对应 POST 请求和 GET 请求; - 取消请求会导致错误,也就是会走上面的
$axios.onError
执行,但其实这个错误不是真正的错误,没必要通过 Message 组件通知用户,所以可以添加判断if (!axios.isCancel(error))
,只有非请求取消错误才报错; - 有时候会遇到需要连续发出请求的地方,可以通过给请求加上个参数解决,比如:
needRepeat: Math.round(Date.now() * Math.random())
,这样两个请求就会因为请求参数中needRepeat
不同而被判定为两个不同的请求。
跨域
使用 proxy 解决浏览器跨域请求问题,安装:npm i @nuxtjs/proxy
。
在 nuxt.config.js 中进行配置:
axios: { proxy: true // 开启 axios 跨域 },
proxy: {
'/api/': {
target: 'http://dev.frontend.api.xxx.com',
changeOrigin: true,
pathRewrite: {}
}
}
注意: '/api/'
这里一定要写,否则所有的请求都被代理了,打开页面直接就会跳到 target 的地址去了。