前端实现接口轮类
监听路由变化
const { history } = window
// 响应pushState和replaceState的监听事件名称
export const STATE_CHANGE_NAME = 'stateChange'
/**
* 重写history的pushState和replaceState
* @param action {String} pushState|replaceState
* @return {Function}
*/
function wrapState(action) {
// 获取原始定义
const raw = history[action]
return function () {
// 经过包装的pushState或replaceState
const wrapper = raw.apply(this, arguments)
// 定义名为action的事件
const e = new Event(STATE_CHANGE_NAME)
// 将调用pushState或replaceState时的参数作为stateInfo属性放到事件参数event上
e.stateInfo = {
action,
path: arguments[2],
}
// 调用pushState或replaceState时触发该事件
window.dispatchEvent(e)
return wrapper
}
}
// 修改原始定义
history.pushState = wrapState('pushState')
history.replaceState = wrapState('replaceState')
/**
* 监听并响应路由变更事件
* @param {*} fc 响应事件
* @param {*} options
* @returns {Function} 移除事件函数
*/
export default function useRouteChange(fc, options = {}) {
const { hashChange = true } = options
const routeChange = (event) => {
const { singleSpaTrigger } = event
// NOTE:pushState & replaceState 单独交给 STATE_CHANGE_NAME 事件去监听处理
if (['pushState', 'replaceState'].includes(singleSpaTrigger)) return
typeof fc === 'function' && fc(event)
}
window.addEventListener('popstate', routeChange, false)
window.addEventListener(STATE_CHANGE_NAME, routeChange, false)
hashChange && window.addEventListener('hashchange', routeChange, false)
const remove = () => {
window.removeEventListener('popstate', routeChange)
window.removeEventListener(STATE_CHANGE_NAME, routeChange)
window.removeEventListener('hashchange', routeChange)
}
return remove
}
Polling类(接口轮训)
import useRouteChange from './useRouteChange'
const BASE_OPTIONS = {
delay: 5000, // 默认5s一轮询
stopWhenRouteChange: true,
}
function nulloop() {}
/**
* 接口轮询类
* - 启动轮询
* - 关闭轮询
* - 页面visible切换时 自动停止轮询
*/
export default class Polling {
/**
* @param {Function} fc Promise化的待轮询方法,通常为一个http请求方法
* @param {Object} options 配置项
*/
constructor(fc, options) {
this.fc = typeof fc === 'function' ? fc : nulloop
this.timer = null // 轮询定时器
this.options = {
...BASE_OPTIONS,
...(options || {}),
}
this.isPolling = false // 是否在轮询中
this.removeRouteChange = null
window.addEventListener('visibilitychange', this._onVisibilityChange, false)
if (this.options.stopWhenRouteChange) {
this.removeRouteChange = useRouteChange(this._onRouteChange)
}
}
// 添加 routerchange 时停止轮询的逻辑
_onRouteChange = (event) => {
console.log('[polling]_onRouteChange:', event)
this.stop()
}
_onVisibilityChange = () => {
if (document.hidden) {
console.log('[polling]页面被隐藏了')
this._innerStop()
} else {
console.log('[polling]页面重新激活了')
// 手动中断了 则不再继续轮询
if (this.isPolling) {
this.start(true)
}
}
}
async _exec() {
try {
await this.fc()
} catch (e) {
console.error('[polling] error: ', e)
}
}
_innerStop() {
this.timer && clearTimeout(this.timer)
this.timer = null
}
/**
* 开始启动轮询(对外暴露API)
* @param {Boolean} immediate 是否立即执行方法(通常无需传参)
* @returns
*/
async start(immediate = false) {
// 正在轮询中则无需重复调用
if (this.isPolling && !immediate) return
console.log('[polling]...start...')
this.isPolling = true
this._innerStop()
await this._exec()
const loop = () => {
this.timer = setTimeout(async () => {
// 还在轮询中才继续执行
if (this.isPolling) {
await this._exec()
loop()
}
}, this.options.delay)
}
loop()
}
/**
* 中断/暂停轮询(对外暴露API)
*/
stop() {
console.log('[polling]...stop...')
this._innerStop()
this.isPolling = false
}
/* 销毁轮询实例 */
destory() {
window.removeEventListener('visibilitychange', this._onVisibilityChange)
this.removeRouteChange && this.removeRouteChange()
this.stop()
this.fc = nulloop
}
}
使用教程
// 引入Polling类
import Polling from '@/utils/polling'
/**
* 第一个参数传入一个函数,该函数为将要轮训接口的方法
* 第二个参数传入配置项入轮训时间等
* 该类对外暴露pollingInst.start(), pollingInst.stop(), pollingInst.destory() 三个方法
*/
const pollingInst = new Polling(pollingResult, {})
// 样例
const queryDataExtractionParams = async () => {
const [err, res] = await to(queryDataExtractionParamsApi({ recordId }))
if (err) return
Object.assign(pageParams, res.data)
sqlEditor.value.setValue(res.data.customerSql)
if (res.data) pollingInst.start() // 开始轮训
}