hookehuyr

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

- 拆分 normalize_options 为纯函数和保存逻辑函数
- 改进网络监听器注册逻辑,增加返回值判断
- 优化轮询启动逻辑,支持网络恢复时重启
- 完善状态同步规则和注释说明
...@@ -150,8 +150,11 @@ export const build_offline_qr_list = (bill) => { ...@@ -150,8 +150,11 @@ export const build_offline_qr_list = (bill) => {
150 * - 仅在有授权且网络可用时调用 150 * - 仅在有授权且网络可用时调用
151 * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA) 151 * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
152 * @param {boolean} force - 是否强制刷新,默认为 false. force 参数的核心作用是控制是否忽略 “正在进行的缓存请求”, 管的是 “是否允许重复发起请求”,不管 “请求能不能成功执行缓存”。 152 * @param {boolean} force - 是否强制刷新,默认为 false. force 参数的核心作用是控制是否忽略 “正在进行的缓存请求”, 管的是 “是否允许重复发起请求”,不管 “请求能不能成功执行缓存”。
153 - * @returns {Promise<void>} 153 + * @returns 不同情况返回值不一样
154 + * - 成功时包含格式化后的预约记录项列表
155 + * - 失败时包含错误信息(如网络错误、授权失败等)
154 */ 156 */
157 +
155 export const refresh_offline_booking_cache = async ({ force = false } = {}) => { 158 export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
156 // 1. 检查是否有正在进行的刷新请求 159 // 1. 检查是否有正在进行的刷新请求
157 // 2. 如果有,且 force 为 false,则直接返回该 Promise 160 // 2. 如果有,且 force 为 false,则直接返回该 Promise
...@@ -179,6 +182,7 @@ export const refresh_offline_booking_cache = async ({ force = false } = {}) => { ...@@ -179,6 +182,7 @@ export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
179 // 过滤出状态为3(已完成)的记录 182 // 过滤出状态为3(已完成)的记录
180 const normalized = data.map(normalize_bill_item).filter((item) => item && item.pay_id && item.status == 3) 183 const normalized = data.map(normalize_bill_item).filter((item) => item && item.pay_id && item.status == 3)
181 if (normalized.length > 0) { 184 if (normalized.length > 0) {
185 + // TAG: 核心逻辑:将过滤后的记录存储到本地缓存
182 Taro.setStorageSync(OFFLINE_BOOKING_CACHE_KEY, normalized) 186 Taro.setStorageSync(OFFLINE_BOOKING_CACHE_KEY, normalized)
183 } 187 }
184 } 188 }
......
...@@ -19,6 +19,14 @@ import { get_network_type, is_usable_network } from '@/utils/network' ...@@ -19,6 +19,14 @@ import { get_network_type, is_usable_network } from '@/utils/network'
19 * @property {Boolean} has_network_listener 是否已注册网络监听器 19 * @property {Boolean} has_network_listener 是否已注册网络监听器
20 * @property {Function} network_listener 网络监听器 20 * @property {Function} network_listener 网络监听器
21 * @property {Promise} network_listener_promise 网络监听器Promise 21 * @property {Promise} network_listener_promise 网络监听器Promise
22 + *
23 + * 状态同步规则(app_enabled 与 ref_count):
24 + * - app_enabled = true 时,ref_count >= 1(至少有一个使用者)
25 + * - app_enabled = false 时,ref_count = 0(无使用者)
26 + * - enable_offline_booking_cache_polling 会设置 app_enabled = true
27 + * - disable_offline_booking_cache_polling 会设置 app_enabled = false
28 + * - acquire_polling_ref 会增加 ref_count
29 + * - release_polling_ref 会减少 ref_count,降为0时触发清理
22 */ 30 */
23 31
24 /** @type {PollingState} */ 32 /** @type {PollingState} */
...@@ -36,13 +44,21 @@ const polling_state = { ...@@ -36,13 +44,21 @@ const polling_state = {
36 } 44 }
37 45
38 /** 46 /**
39 - * @description: 缓存最后一次 options(用于网络恢复时重启轮询 47 + * @description: 规范化选项参数(纯函数,无副作用
40 * @param {Object} options 选项 48 * @param {Object} options 选项
41 - * @return {Object} 最后一次选项 49 + * @return {Object} 规范化后的选项
42 */ 50 */
43 -
44 const normalize_options = (options) => { 51 const normalize_options = (options) => {
45 - polling_state.last_options = options || polling_state.last_options || {} 52 + return options || {}
53 +}
54 +
55 +/**
56 + * @description: 保存最后一次选项(用于网络恢复时重启轮询)
57 + * @param {Object} options 选项
58 + * @return {Object} 保存后的选项
59 + */
60 +const save_last_options = (options) => {
61 + if (options) polling_state.last_options = options
46 return polling_state.last_options 62 return polling_state.last_options
47 } 63 }
48 64
...@@ -58,13 +74,16 @@ const normalize_options = (options) => { ...@@ -58,13 +74,16 @@ const normalize_options = (options) => {
58 * @param {Boolean} options.force 是否强制刷新 74 * @param {Boolean} options.force 是否强制刷新
59 */ 75 */
60 const run_refresh_once = async (options) => { 76 const run_refresh_once = async (options) => {
61 - if (polling_state.in_flight) return // 核心防重复——如果正在刷新,直接返回 77 + // 前置检查:不满足轮询条件时直接返回(网络不可用或无引用)
62 - // 标记为“正在刷新” 78 + if (!should_run_polling()) return
79 + // 核心防重复——如果正在刷新,直接返回
80 + if (polling_state.in_flight) return
81 + // 标记为"正在刷新"
63 polling_state.in_flight = true 82 polling_state.in_flight = true
64 try { 83 try {
65 await refresh_offline_booking_cache({ force: !!options?.force }) 84 await refresh_offline_booking_cache({ force: !!options?.force })
66 } finally { 85 } finally {
67 - // 刷新完成后,标记为“刷新完成” 86 + // 刷新完成后,标记为"刷新完成"
68 polling_state.in_flight = false 87 polling_state.in_flight = false
69 } 88 }
70 } 89 }
...@@ -81,8 +100,16 @@ const update_network_usable = async () => { ...@@ -81,8 +100,16 @@ const update_network_usable = async () => {
81 /** 100 /**
82 * @description: 判断是否需要运行轮询 101 * @description: 判断是否需要运行轮询
83 * @return {Boolean} 是否需要运行轮询 102 * @return {Boolean} 是否需要运行轮询
103 + *
104 + * 返回 false 的条件:
105 + * 1. ref_count <= 0:无使用者,无需轮询
106 + * 2. network_usable === false:网络不可用,无需轮询
107 + * 3. network_usable === null:网络状态未初始化,避免在无监听器时误判
108 + *
109 + * 返回 true 的条件:
110 + * 1. ref_count > 0:至少有一个使用者
111 + * 2. network_usable === true:网络可用
84 */ 112 */
85 -
86 const should_run_polling = () => { 113 const should_run_polling = () => {
87 if (polling_state.ref_count <= 0) return false 114 if (polling_state.ref_count <= 0) return false
88 if (polling_state.network_usable === false) return false 115 if (polling_state.network_usable === false) return false
...@@ -92,9 +119,8 @@ const should_run_polling = () => { ...@@ -92,9 +119,8 @@ const should_run_polling = () => {
92 119
93 /** 120 /**
94 * @description: 确保网络监听器已注册 121 * @description: 确保网络监听器已注册
95 - * @return {Promise} 网络监听器Promise 122 + * @return {Promise<Boolean>} 是否注册成功(true=成功,false=失败)
96 */ 123 */
97 -
98 const ensure_network_listener = async () => { 124 const ensure_network_listener = async () => {
99 /** 125 /**
100 * 代码优先通过两个条件判断避免重复执行监听器逻辑 126 * 代码优先通过两个条件判断避免重复执行监听器逻辑
...@@ -104,12 +130,13 @@ const ensure_network_listener = async () => { ...@@ -104,12 +130,13 @@ const ensure_network_listener = async () => {
104 130
105 if (polling_state.has_network_listener) { 131 if (polling_state.has_network_listener) {
106 await update_network_usable() 132 await update_network_usable()
107 - return 133 + return true
108 } 134 }
109 135
110 if (polling_state.network_listener_promise) { 136 if (polling_state.network_listener_promise) {
111 await polling_state.network_listener_promise 137 await polling_state.network_listener_promise
112 - return 138 + // 等待注册完成后检查是否成功
139 + return polling_state.has_network_listener
113 } 140 }
114 141
115 // 立即执行异步的监听器注册流程(标记状态→更新网络可用性→定义回调→注册监听) 142 // 立即执行异步的监听器注册流程(标记状态→更新网络可用性→定义回调→注册监听)
...@@ -125,14 +152,16 @@ const ensure_network_listener = async () => { ...@@ -125,14 +152,16 @@ const ensure_network_listener = async () => {
125 const type = res?.networkType || 'unknown' 152 const type = res?.networkType || 'unknown'
126 polling_state.network_usable = is_connected && is_usable_network(type) 153 polling_state.network_usable = is_connected && is_usable_network(type)
127 154
128 - // 网络不可用时,停止轮询 155 + // 改进:不再主动停止轮询,由 run_refresh_once 中的 should_run_polling() 前置检查控制
129 - if (!polling_state.network_usable) { 156 + // 优势:
130 - stop_offline_booking_cache_polling() 157 + // 1. 避免网络恢复时需要额外的重启逻辑
131 - return 158 + // 2. 保持定时器稳定,避免频繁启动/停止
132 - } 159 + // 3. 网络不可用时,刷新操作会在 run_refresh_once 中被前置检查过滤掉
133 160
134 - if (should_run_polling()) { 161 + // 网络恢复时,确保轮询正在运行(处理之前因网络不可用而可能停止的轮询)
135 - start_offline_booking_cache_polling(polling_state.last_options || {}) 162 + if (polling_state.network_usable && should_run_polling()) {
163 + // 传入 restart: true,支持重启逻辑
164 + start_offline_booking_cache_polling({ ...(polling_state.last_options || {}), restart: true })
136 } 165 }
137 } 166 }
138 167
...@@ -148,12 +177,15 @@ const ensure_network_listener = async () => { ...@@ -148,12 +177,15 @@ const ensure_network_listener = async () => {
148 })() 177 })()
149 178
150 try { 179 try {
151 - // 等待网络监听器初始化完成, Taro.onNetworkStatusChange 180 + // 等待网络监听器初始化完成
152 await polling_state.network_listener_promise 181 await polling_state.network_listener_promise
153 } finally { 182 } finally {
154 - // 等待注册流程完成后,强制清空 Promise 缓存(finally 块),保证下次执行逻辑时状态干净 183 + // 等待注册流程完成后,强制清空 Promise 缓存(finally 块),保证下次执行逻辑时状态干净
155 polling_state.network_listener_promise = null 184 polling_state.network_listener_promise = null
156 } 185 }
186 +
187 + // 返回注册是否成功
188 + return polling_state.has_network_listener
157 } 189 }
158 190
159 /** 191 /**
...@@ -201,15 +233,27 @@ const teardown_network_listener = () => { ...@@ -201,15 +233,27 @@ const teardown_network_listener = () => {
201 * @param {Number} options.interval_ms 轮询间隔,单位毫秒 233 * @param {Number} options.interval_ms 轮询间隔,单位毫秒
202 * @param {Boolean} options.immediate 是否立即刷新一次 234 * @param {Boolean} options.immediate 是否立即刷新一次
203 * @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache) 235 * @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache)
236 + * @param {Boolean} options.restart 是否为重启操作(网络恢复时调用)
204 */ 237 */
205 -
206 const start_offline_booking_cache_polling = (options) => { 238 const start_offline_booking_cache_polling = (options) => {
207 - normalize_options(options) 239 + options = normalize_options(options)
208 if (!should_run_polling()) return // 不满足轮询条件直接返回 240 if (!should_run_polling()) return // 不满足轮询条件直接返回
241 +
209 const interval_ms = Number(options?.interval_ms || 60000) 242 const interval_ms = Number(options?.interval_ms || 60000)
210 - if (polling_state.running) return // 核心防重复——如果已经在轮询,直接返回 243 + const is_restart = options?.restart === true
211 244
212 - polling_state.running = true // 标记为“正在轮询” 245 + // 改进:区分首次启动和重启的防重逻辑
246 + // 首次启动时,如果已经在轮询则直接返回(防重复启动)
247 + // 重启时,需要清除旧定时器并重新建立(支持网络恢复时重启)
248 + if (polling_state.running && !is_restart) return
249 +
250 + // 如果是重启或定时器已存在,先清除旧定时器
251 + if (is_restart && polling_state.timer_id) {
252 + clearInterval(polling_state.timer_id)
253 + polling_state.timer_id = null
254 + }
255 +
256 + polling_state.running = true // 标记为"正在轮询"
213 257
214 // 立即刷新一次,确保轮询开始时数据是最新的 258 // 立即刷新一次,确保轮询开始时数据是最新的
215 if (options?.immediate !== false) { 259 if (options?.immediate !== false) {
...@@ -245,15 +289,18 @@ const stop_offline_booking_cache_polling = () => { ...@@ -245,15 +289,18 @@ const stop_offline_booking_cache_polling = () => {
245 289
246 /** 290 /**
247 * @description: 增加轮询引用计数 291 * @description: 增加轮询引用计数
248 - * 核心动作:将全局的 ref_count 加 1,代表 “又多了一个场景需要使用轮询功能” 292 + * 核心动作:将全局的 ref_count 加 1,代表 "又多了一个场景需要使用轮询功能"
249 * @param {Object} options 选项 293 * @param {Object} options 选项
250 */ 294 */
251 -
252 const acquire_polling_ref = (options) => { 295 const acquire_polling_ref = (options) => {
253 - normalize_options(options) 296 + save_last_options(options)
254 polling_state.ref_count += 1 297 polling_state.ref_count += 1
255 - // 增加引用计数后,确保网络监听器已注册并初始化完成,然后启动轮询 298 + // 改进:检查网络监听器注册结果,只有成功后才启动轮询
256 - ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {})) 299 + ensure_network_listener().then((success) => {
300 + if (success && polling_state.last_options) {
301 + start_offline_booking_cache_polling(polling_state.last_options)
302 + }
303 + })
257 } 304 }
258 305
259 /** 306 /**
...@@ -277,16 +324,19 @@ const release_polling_ref = () => { ...@@ -277,16 +324,19 @@ const release_polling_ref = () => {
277 * @param {Boolean} options.immediate 是否立即刷新一次 324 * @param {Boolean} options.immediate 是否立即刷新一次
278 * @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache) 325 * @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache)
279 */ 326 */
280 -
281 export const enable_offline_booking_cache_polling = (options) => { 327 export const enable_offline_booking_cache_polling = (options) => {
282 - normalize_options(options) 328 + save_last_options(options)
283 /** 329 /**
284 - * 核心目的:对 app_enabled=true 的场景做兜底,确保轮询在 “已启用但异常停止” 时能被主动恢复,而非被动等待网络变化; 330 + * 核心目的:对 app_enabled=true 的场景做兜底,确保轮询在 "已启用但异常停止" 时能被主动恢复,而非被动等待网络变化;
285 * 执行逻辑:先保证网络监听器(轮询的依赖)就绪,再尝试启动轮询,且利用 start_offline_booking_cache_polling 的幂等性避免重复; 331 * 执行逻辑:先保证网络监听器(轮询的依赖)就绪,再尝试启动轮询,且利用 start_offline_booking_cache_polling 的幂等性避免重复;
286 - * 设计思维:体现了 “主动调用需即时生效” 的用户体验考量,以及 “依赖前置检查” 的工程化思维 —— 先保证依赖(监听器)就绪,再执行核心操作(启动轮询)。 332 + * 设计思维:体现了 "主动调用需即时生效" 的用户体验考量,以及 "依赖前置检查" 的工程化思维 —— 先保证依赖(监听器)就绪,再执行核心操作(启动轮询)。
287 */ 333 */
288 if (polling_state.app_enabled) { 334 if (polling_state.app_enabled) {
289 - ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {})) 335 + ensure_network_listener().then((success) => {
336 + if (success && polling_state.last_options) {
337 + start_offline_booking_cache_polling(polling_state.last_options)
338 + }
339 + })
290 return 340 return
291 } 341 }
292 polling_state.app_enabled = true 342 polling_state.app_enabled = true
......