Vue2中并发、继发请求的loading设置

时间:2021-2-20 作者:admin

前言

项目中,全局loading一般是在整体内容区域进行展示,项目在调用接口的时候展示,接口调用完成后隐藏,防止在页面接口请求过程中用户进行意料之外的操作。这次就是对loading的一个处理过程

项目构成:vue2+axios+elementUI

像elementUI之类的UI框架中都会封装好了loading的样式结构之类的,如果没有用这类框架的话也可以自己封装一个loading。

最开始瞄一眼需求,开始就想当然的认为在请求拦截器中把loading设置为true,在响应拦截器中设置为false不就行了,easy~

关于请求、响应拦截器可查看官方文档
axios文档: github.com/axios/axios

这里稍微解释下请求响应拦截器:拦截器会在请求或响应被 then 或 catch 处理前拦截它们。

比如去吃火锅的话,羊肉就相当于我们的请求。

  • 羊肉下锅之前要切片(请求拦截器,在请求之前对其进行处理)
  • 在肉煮完然后吃下的过程中,蘸蘸料(响应拦截器,得到结果之后同时在最终使用之前,对其处理)

上面那个想当然的方法确实可以实现:不过有特定场景,比如我的页面只有一个请求,那么这个效果肯定是对的。但是一般而言(或者说绝大多数情况下),我们的页面都会有并发和继发请求的情况。所以要对这两种方式进行处理。

并发、继发请求简介

并发请求
请求同时发出,但是响应时间不定。例如:

created() {
  axios.get()
  axios.post()
  axios.get()
  // 或者是:
  const res1Promise = xxxx()
  const res2Promise = xxxx()
  const res1 = await res1Promise
  const res2 = await res2Promise
}

上面的这两种方式的请求都属于并发请求。

继发请求
一个请求发出且接收到响应之后再发出下一个请求,这样就是继发请求。常见的比如请求成功回调函数中继续发送请求

created() {
  axios.get().then(res => {
    axios.post()
  })
  // 或者是
  const res1 = await xxxx()
  const res2 = await xxxxx()
}

像上面这两种都属于继发请求。

解决思路

并发请求解决思路:

vuex中存储一个count值,每次请求发起之前count+1 ,响应完成之后count-1,当count为0的时候证明所有请求完成了。

vuex

const state = {
  requestCount: 0
}
const mutations = {
ADD_REQUEST_COUNT: (state) => {
    state.requestCount = state.requestCount + 1
  },
  SUBTRACT_REQUEST_COUNT: (state) => {
    state.requestCount = state.requestCount - 1
    if (state.requestCount < 0) state.requestCount = 0
  },
}

axios设置

注意这里面定义了配置过后新的axios为service,下文中也都是使用的service

const service = axios.create({....})

service.interceptors.request.use(config => {
    store.commit('global/ADD_REQUEST_COUNT')
  return config
}, (err) => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  return Promise.resolve(err)
})
service.interceptors.response.use(data => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  return data
}, err => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  return Promise.reject(err)
})

继发请求解决思路:

继发请求是在当前某个请求完成之后紧接着发起下一个请求,如果是继发请求的话,就不能再响应完成之后立刻结束loading,要在完成之后一段时间之内没有新的请求的话才会结束loading。
同理:我们每个请求发出之前也不能立即设置loading为true,因为如果是继发请求的话loading本身就已经为true了
所以可以在响应完成之后记录一下本次响应完成的时间,然后在请求拦截器中进行时间间隔判断,时间间隔过短的话可以认为是继发请求。我设置的继发请求时间间隔设置的100ms
vuex

const state = {
  prevRequsetEndTime: 0,
  requestEndTimer: null,
}

const mutations = {
  SET_REQUEST_END_TIMER: (state, requestEndTimer) => {
    state.requestEndTimer = requestEndTimer
  },
  SET_PREV_REQUEST_END_TIME: (state, prevRequsetEndTime) => {
    state.prevRequsetEndTime = prevRequsetEndTime
  },
}

axios

const requestContinue = 100
// 请求拦截器
service.interceptors.request.use(config => {
    const { requestCount, prevRequsetEndTime, requestEndTimer } = store.getters
    const nowTime = +new Date()
    if (nowTime - prevRequsetEndTime < requestContinue) {
      clearTimeout(requestEndTimer)
    } else if (requestCount === 0) {
      store.commit('global/SET_LOADING', true)
    }
    store.commit('global/ADD_REQUEST_COUNT')
  return config
}, (err) => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  const { requestCount } = store.getters
  if (requestCount === 0) {
    const timer = setTimeout(() => {
      store.commit('global/SET_LOADING', false)
    }, requestContinue)
    store.commit('global/SET_REQUEST_END_TIMER', timer)
  }
  return Promise.resolve(err)
})
// 响应拦截器
service.interceptors.response.use(data => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  store.commit('global/SET_PREV_REQUEST_END_TIME', +new Date())
    const { requestCount } = store.getters
    if (requestCount === 0) {
      const timer = setTimeout(() => {
        store.commit('global/SET_LOADING', false)
      }, requestContinue)
      store.commit('global/SET_REQUEST_END_TIMER', timer)
    }
  return data.data
}, err => {
  store.commit('global/SUBTRACT_REQUEST_COUNT')
  const { requestCount } = store.getters
  if (requestCount === 0) {
    const timer = setTimeout(() => {
      store.commit('global/SET_LOADING', false)
    }, requestContinue)
    store.commit('global/SET_REQUEST_END_TIMER', timer)
  }
  return Promise.reject(err)
})

特定接口不需要loading

项目中有某些接口是不需要全局loading的,项目中未使用websocket的话常见的比如:ajax轮询,也就是定时器每隔一段时间调用一次接口,这种情况下loading的true与false其实和这种请求是没有关联的。
所以现在需要做的是区分这种不需要loading的特殊请求和一般请求
axios文档中有请求配置这一项,在请求时可以配置这一项,所以可以设置不需要请求的showLoading为false,默认为true

关于这个请求配置,示例如下:

service({
  method: 'get',
  url,
  loadingConfig: { showLoading: true }
})

// 在请求拦截器中,可以通过config.loadingConfig获取参数
service.interceptors.request.use(config => {
  if(config.loadingConfig.showLoading) {
    ...
  }
})

// 在响应拦截器中,可以通过data.config.loadingConfig.showLoading获取参数
service.interceptors.request.use(data => {
  if(data.config.loadingConfig.showLoading) {
    ...
  }
})

这样就可以通过设置config来区分哪些是需要设置全局loading的

注意:
内容区域与表格loading会有冲突

一般而言表格区域会额外加一个表格的loading,然后全局也有一个loading,可以为了防止两者冲突可以统一设置表格接口showLoading为false。

因为公司后台使用的是RESTful API,所以在axios统一配置文件中导出了例如get post delete请求等,统一配置就可以在这里进行
例如:

export const getRequest = (url, config = { showLoading: true }) => {
  const params = url.split('?')[1]
  if (params && params.indexOf('page') !== -1 && params.indexOf('size') !== -1) {
    config = { showLoading: false }
  }
  return service({
    method: 'get',
    url,
    loadingConfig: config
  })
}

END

如果有好的实现方法还望不吝赐教

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。