hookehuyr

refactor(offline-booking): 优化离线预约缓存轮询逻辑

- 拆分 normalize_options 为纯函数和保存逻辑函数
- 改进网络监听器注册逻辑,增加返回值判断
- 优化轮询启动逻辑,支持网络恢复时重启
- 完善状态同步规则和注释说明
......@@ -150,8 +150,11 @@ export const build_offline_qr_list = (bill) => {
* - 仅在有授权且网络可用时调用
* - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
* @param {boolean} force - 是否强制刷新,默认为 false. force 参数的核心作用是控制是否忽略 “正在进行的缓存请求”, 管的是 “是否允许重复发起请求”,不管 “请求能不能成功执行缓存”。
* @returns {Promise<void>}
* @returns 不同情况返回值不一样
* - 成功时包含格式化后的预约记录项列表
* - 失败时包含错误信息(如网络错误、授权失败等)
*/
export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
// 1. 检查是否有正在进行的刷新请求
// 2. 如果有,且 force 为 false,则直接返回该 Promise
......@@ -179,6 +182,7 @@ export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
// 过滤出状态为3(已完成)的记录
const normalized = data.map(normalize_bill_item).filter((item) => item && item.pay_id && item.status == 3)
if (normalized.length > 0) {
// TAG: 核心逻辑:将过滤后的记录存储到本地缓存
Taro.setStorageSync(OFFLINE_BOOKING_CACHE_KEY, normalized)
}
}
......
......@@ -19,6 +19,14 @@ import { get_network_type, is_usable_network } from '@/utils/network'
* @property {Boolean} has_network_listener 是否已注册网络监听器
* @property {Function} network_listener 网络监听器
* @property {Promise} network_listener_promise 网络监听器Promise
*
* 状态同步规则(app_enabled 与 ref_count):
* - app_enabled = true 时,ref_count >= 1(至少有一个使用者)
* - app_enabled = false 时,ref_count = 0(无使用者)
* - enable_offline_booking_cache_polling 会设置 app_enabled = true
* - disable_offline_booking_cache_polling 会设置 app_enabled = false
* - acquire_polling_ref 会增加 ref_count
* - release_polling_ref 会减少 ref_count,降为0时触发清理
*/
/** @type {PollingState} */
......@@ -36,13 +44,21 @@ const polling_state = {
}
/**
* @description: 缓存最后一次 options(用于网络恢复时重启轮询
* @description: 规范化选项参数(纯函数,无副作用
* @param {Object} options 选项
* @return {Object} 最后一次选项
* @return {Object} 规范化后的选项
*/
const normalize_options = (options) => {
polling_state.last_options = options || polling_state.last_options || {}
return options || {}
}
/**
* @description: 保存最后一次选项(用于网络恢复时重启轮询)
* @param {Object} options 选项
* @return {Object} 保存后的选项
*/
const save_last_options = (options) => {
if (options) polling_state.last_options = options
return polling_state.last_options
}
......@@ -58,13 +74,16 @@ const normalize_options = (options) => {
* @param {Boolean} options.force 是否强制刷新
*/
const run_refresh_once = async (options) => {
if (polling_state.in_flight) return // 核心防重复——如果正在刷新,直接返回
// 标记为“正在刷新”
// 前置检查:不满足轮询条件时直接返回(网络不可用或无引用)
if (!should_run_polling()) return
// 核心防重复——如果正在刷新,直接返回
if (polling_state.in_flight) return
// 标记为"正在刷新"
polling_state.in_flight = true
try {
await refresh_offline_booking_cache({ force: !!options?.force })
} finally {
// 刷新完成后,标记为“刷新完成”
// 刷新完成后,标记为"刷新完成"
polling_state.in_flight = false
}
}
......@@ -81,8 +100,16 @@ const update_network_usable = async () => {
/**
* @description: 判断是否需要运行轮询
* @return {Boolean} 是否需要运行轮询
*
* 返回 false 的条件:
* 1. ref_count <= 0:无使用者,无需轮询
* 2. network_usable === false:网络不可用,无需轮询
* 3. network_usable === null:网络状态未初始化,避免在无监听器时误判
*
* 返回 true 的条件:
* 1. ref_count > 0:至少有一个使用者
* 2. network_usable === true:网络可用
*/
const should_run_polling = () => {
if (polling_state.ref_count <= 0) return false
if (polling_state.network_usable === false) return false
......@@ -92,9 +119,8 @@ const should_run_polling = () => {
/**
* @description: 确保网络监听器已注册
* @return {Promise} 网络监听器Promise
* @return {Promise<Boolean>} 是否注册成功(true=成功,false=失败)
*/
const ensure_network_listener = async () => {
/**
* 代码优先通过两个条件判断避免重复执行监听器逻辑
......@@ -104,12 +130,13 @@ const ensure_network_listener = async () => {
if (polling_state.has_network_listener) {
await update_network_usable()
return
return true
}
if (polling_state.network_listener_promise) {
await polling_state.network_listener_promise
return
// 等待注册完成后检查是否成功
return polling_state.has_network_listener
}
// 立即执行异步的监听器注册流程(标记状态→更新网络可用性→定义回调→注册监听)
......@@ -125,14 +152,16 @@ const ensure_network_listener = async () => {
const type = res?.networkType || 'unknown'
polling_state.network_usable = is_connected && is_usable_network(type)
// 网络不可用时,停止轮询
if (!polling_state.network_usable) {
stop_offline_booking_cache_polling()
return
}
// 改进:不再主动停止轮询,由 run_refresh_once 中的 should_run_polling() 前置检查控制
// 优势:
// 1. 避免网络恢复时需要额外的重启逻辑
// 2. 保持定时器稳定,避免频繁启动/停止
// 3. 网络不可用时,刷新操作会在 run_refresh_once 中被前置检查过滤掉
if (should_run_polling()) {
start_offline_booking_cache_polling(polling_state.last_options || {})
// 网络恢复时,确保轮询正在运行(处理之前因网络不可用而可能停止的轮询)
if (polling_state.network_usable && should_run_polling()) {
// 传入 restart: true,支持重启逻辑
start_offline_booking_cache_polling({ ...(polling_state.last_options || {}), restart: true })
}
}
......@@ -148,12 +177,15 @@ const ensure_network_listener = async () => {
})()
try {
// 等待网络监听器初始化完成, Taro.onNetworkStatusChange
// 等待网络监听器初始化完成
await polling_state.network_listener_promise
} finally {
// 等待注册流程完成后,强制清空 Promise 缓存(finally 块),保证下次执行逻辑时状态干净
// 等待注册流程完成后,强制清空 Promise 缓存(finally 块),保证下次执行逻辑时状态干净
polling_state.network_listener_promise = null
}
// 返回注册是否成功
return polling_state.has_network_listener
}
/**
......@@ -201,15 +233,27 @@ const teardown_network_listener = () => {
* @param {Number} options.interval_ms 轮询间隔,单位毫秒
* @param {Boolean} options.immediate 是否立即刷新一次
* @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache)
* @param {Boolean} options.restart 是否为重启操作(网络恢复时调用)
*/
const start_offline_booking_cache_polling = (options) => {
normalize_options(options)
options = normalize_options(options)
if (!should_run_polling()) return // 不满足轮询条件直接返回
const interval_ms = Number(options?.interval_ms || 60000)
if (polling_state.running) return // 核心防重复——如果已经在轮询,直接返回
const is_restart = options?.restart === true
// 改进:区分首次启动和重启的防重逻辑
// 首次启动时,如果已经在轮询则直接返回(防重复启动)
// 重启时,需要清除旧定时器并重新建立(支持网络恢复时重启)
if (polling_state.running && !is_restart) return
polling_state.running = true // 标记为“正在轮询”
// 如果是重启或定时器已存在,先清除旧定时器
if (is_restart && polling_state.timer_id) {
clearInterval(polling_state.timer_id)
polling_state.timer_id = null
}
polling_state.running = true // 标记为"正在轮询"
// 立即刷新一次,确保轮询开始时数据是最新的
if (options?.immediate !== false) {
......@@ -245,15 +289,18 @@ const stop_offline_booking_cache_polling = () => {
/**
* @description: 增加轮询引用计数
* 核心动作:将全局的 ref_count 加 1,代表 “又多了一个场景需要使用轮询功能”
* 核心动作:将全局的 ref_count 加 1,代表 "又多了一个场景需要使用轮询功能"
* @param {Object} options 选项
*/
const acquire_polling_ref = (options) => {
normalize_options(options)
save_last_options(options)
polling_state.ref_count += 1
// 增加引用计数后,确保网络监听器已注册并初始化完成,然后启动轮询
ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {}))
// 改进:检查网络监听器注册结果,只有成功后才启动轮询
ensure_network_listener().then((success) => {
if (success && polling_state.last_options) {
start_offline_booking_cache_polling(polling_state.last_options)
}
})
}
/**
......@@ -277,16 +324,19 @@ const release_polling_ref = () => {
* @param {Boolean} options.immediate 是否立即刷新一次
* @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache)
*/
export const enable_offline_booking_cache_polling = (options) => {
normalize_options(options)
save_last_options(options)
/**
* 核心目的:对 app_enabled=true 的场景做兜底,确保轮询在 “已启用但异常停止” 时能被主动恢复,而非被动等待网络变化;
* 核心目的:对 app_enabled=true 的场景做兜底,确保轮询在 "已启用但异常停止" 时能被主动恢复,而非被动等待网络变化;
* 执行逻辑:先保证网络监听器(轮询的依赖)就绪,再尝试启动轮询,且利用 start_offline_booking_cache_polling 的幂等性避免重复;
* 设计思维:体现了 “主动调用需即时生效” 的用户体验考量,以及 “依赖前置检查” 的工程化思维 —— 先保证依赖(监听器)就绪,再执行核心操作(启动轮询)。
* 设计思维:体现了 "主动调用需即时生效" 的用户体验考量,以及 "依赖前置检查" 的工程化思维 —— 先保证依赖(监听器)就绪,再执行核心操作(启动轮询)。
*/
if (polling_state.app_enabled) {
ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {}))
ensure_network_listener().then((success) => {
if (success && polling_state.last_options) {
start_offline_booking_cache_polling(polling_state.last_options)
}
})
return
}
polling_state.app_enabled = true
......