前言
最近在开发小程序的时候,遇到一个需求,需要更新页面数据再更新,如果接口数据不更新则不更新页面 通常来说实现这一功能,大都使用轮询+数据对比来实现 但是处理这种方法,我们还有四种方案可以更高效的来监控数据,实现这一功能
方案
- WebSocket 方案(推荐):
// websocket-service.js
export default {
data() {
return {
ws: null,
retryCount: 0,
maxRetry: 3,
heartbeatTimer: null
}
},
methods: {
initWebSocket() {
try {
this.ws = uni.connectSocket({
url: 'your-websocket-url',
success: () => {
this.initWebSocketEvents()
}
})
} catch (e) {
console.error('WebSocket 连接失败:', e)
this.reconnect()
}
},
initWebSocketEvents() {
this.ws.onOpen(() => {
console.log('WebSocket 已连接')
this.retryCount = 0
this.startHeartbeat()
})
this.ws.onMessage((res) => {
try {
const data = JSON.parse(res.data)
switch (data.type) {
case 'pool_update':
// 收到奖池更新通知,获取最新数据
this.fetchLatestPoolData()
break
case 'pong':
// 心跳响应
break
default:
console.log('收到未知消息类型:', data.type)
}
} catch (e) {
console.error('消息处理错误:', e)
}
})
this.ws.onClose(() => {
console.log('WebSocket 已断开')
this.stopHeartbeat()
this.reconnect()
})
this.ws.onError((error) => {
console.error('WebSocket 错误:', error)
this.reconnect()
})
},
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.ws.send({
data: JSON.stringify({ type: 'ping' })
})
}, 30000) // 每30秒发送一次心跳
},
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
},
reconnect() {
if (this.retryCount < this.maxRetry) {
setTimeout(() => {
console.log('尝试重新连接...')
this.retryCount++
this.initWebSocket()
}, 3000 * this.retryCount) // 递增重试间隔
} else {
console.error('WebSocket 重连失败,切换到备用方案')
this.switchToFallback()
}
},
// 备用方案
switchToFallback() {
// 可以切换到 SSE 或轮询
this.initSSE()
}
}
}
- SSE (Server-Sent Events) 方案:
// sse-service.js
export default {
data() {
return {
eventSource: null,
retryCount: 0,
maxRetry: 3
}
},
methods: {
initSSE() {
try {
this.eventSource = new EventSource('your-sse-url')
this.eventSource.onopen = () => {
console.log('SSE 连接已建立')
this.retryCount = 0
}
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'pool_update') {
this.fetchLatestPoolData()
}
} catch (e) {
console.error('SSE 消息处理错误:', e)
}
}
this.eventSource.onerror = (error) => {
console.error('SSE 错误:', error)
this.eventSource.close()
this.reconnectSSE()
}
} catch (e) {
console.error('SSE 初始化失败:', e)
this.reconnectSSE()
}
},
reconnectSSE() {
if (this.retryCount < this.maxRetry) {
setTimeout(() => {
console.log('尝试重新连接 SSE...')
this.retryCount++
this.initSSE()
}, 3000 * this.retryCount)
} else {
console.error('SSE 重连失败,切换到轮询')
this.switchToPolling()
}
}
}
}
- HTTP 长轮询(比普通轮询更高效):
// long-polling-service.js
export default {
data() {
return {
polling: false,
lastUpdateTime: null
}
},
methods: {
async startLongPolling() {
if (this.polling) return
this.polling = true
while (this.polling) {
try {
const res = await this.waitForUpdate()
if (res.hasUpdate) {
await this.fetchLatestPoolData()
}
} catch (e) {
console.error('长轮询错误:', e)
// 出错后等待一段时间再重试
await this.sleep(5000)
}
}
},
async waitForUpdate() {
return new Promise((resolve, reject) => {
uni.request({
url: 'your-api/pool/wait-update',
data: {
lastUpdateTime: this.lastUpdateTime
},
// 设置较长的超时时间
timeout: 30000,
success: (res) => {
if (res.data.code === 0) {
this.lastUpdateTime = res.data.lastUpdateTime
resolve(res.data)
} else {
reject(new Error(res.data.msg))
}
},
fail: reject
})
})
},
stopLongPolling() {
this.polling = false
}
}
}
- 混合方案(推荐):
// hybrid-update-service.js
export default {
data() {
return {
currentStrategy: null,
strategies: {
WEBSOCKET: 'websocket',
SSE: 'sse',
LONG_POLLING: 'longPolling'
}
}
},
methods: {
// 初始化更新监听
initUpdateListener() {
// 优先尝试 WebSocket
this.tryStrategy(this.strategies.WEBSOCKET)
},
async tryStrategy(strategy) {
this.currentStrategy = strategy
switch (strategy) {
case this.strategies.WEBSOCKET:
try {
await this.initWebSocket()
} catch (e) {
// WebSocket 失败,降级到 SSE
this.tryStrategy(this.strategies.SSE)
}
break
case this.strategies.SSE:
try {
await this.initSSE()
} catch (e) {
// SSE 失败,降级到长轮询
this.tryStrategy(this.strategies.LONG_POLLING)
}
break
case this.strategies.LONG_POLLING:
this.startLongPolling()
break
}
},
// 根据网络状况动态调整策略
async adjustStrategy() {
const networkType = await this.getNetworkType()
if (networkType === 'wifi' && this.currentStrategy !== this.strategies.WEBSOCKET) {
// 网络状况好,尝试升级到 WebSocket
this.tryStrategy(this.strategies.WEBSOCKET)
} else if (networkType === 'none') {
// 网络断开,停止所有监听
this.stopAll()
}
}
},
// 监听网络状态变化
onShow() {
uni.onNetworkStatusChange((res) => {
if (res.isConnected) {
this.adjustStrategy()
} else {
this.stopAll()
}
})
}
}
使用建议:
优先级顺序:
WebSocket > SSE > 长轮询 > 普通轮询
选择依据:
WebSocket:
实时性要求高,双向通信频繁
SSE:
只需服务器推送,客户端很少发送数据
长轮询:
简单可靠,对实时性要求不太高
普通轮询:
作为最后的备选方案
实现建议:
- 实现优雅降级机制
- 添加重试和错误处理
- 考虑网络状况动态调整
- 实现心跳检测
- 添加断线重连功能
注意事项:
- 考虑移动端网络特点
- 处理应用前后台切换
- 控制重连次数和间隔
- 做好错误提示
- 注意内存泄漏
需要根据具体场景选择合适的方案,或者采用混合方案以提供更好的用户体验。