* Execute the refresh, tracking in-flight state.
(trigger: RefreshTrigger)
| 309 | * Execute the refresh, tracking in-flight state. |
| 310 | */ |
| 311 | private executeRefresh(trigger: RefreshTrigger): void { |
| 312 | if (this.disposed) return; |
| 313 | |
| 314 | this.lastRefreshStartMs = Date.now(); |
| 315 | |
| 316 | if (this.cooldownTimer) { |
| 317 | clearTimeout(this.cooldownTimer); |
| 318 | this.cooldownTimer = null; |
| 319 | } |
| 320 | |
| 321 | this.inFlight = true; |
| 322 | this.pendingTrigger = null; |
| 323 | |
| 324 | // Capture generation at start so dispose() can fence stale continuations. |
| 325 | const runGeneration = this.lifecycleGeneration; |
| 326 | const isRunStale = () => this.disposed || runGeneration !== this.lifecycleGeneration; |
| 327 | |
| 328 | // Shared cleanup: clear in-flight state and process any queued follow-up. |
| 329 | const finalizeInFlight = () => { |
| 330 | this.inFlight = false; |
| 331 | if (this.pendingBecauseInFlight) { |
| 332 | this.pendingBecauseInFlight = false; |
| 333 | const followupTrigger = this.pendingTrigger ?? "in-flight-followup"; |
| 334 | this.pendingTrigger = null; |
| 335 | this.scheduleWithDelay(this.debounceMs, followupTrigger); |
| 336 | } |
| 337 | }; |
| 338 | |
| 339 | const handleSuccess = () => { |
| 340 | const info: LastRefreshInfo = { timestamp: Date.now(), trigger }; |
| 341 | this._lastRefreshInfo = info; |
| 342 | |
| 343 | finalizeInFlight(); |
| 344 | |
| 345 | try { |
| 346 | this.onRefreshComplete?.(info); |
| 347 | } catch (error) { |
| 348 | console.error("[RefreshController] onRefreshComplete callback threw", error); |
| 349 | } |
| 350 | }; |
| 351 | |
| 352 | const handleFailure = (error: unknown) => { |
| 353 | const errorMessage = error instanceof Error ? error.message : String(error); |
| 354 | this.debug(`onRefresh failed: ${errorMessage}`); |
| 355 | // Do NOT mutate _lastRefreshInfo — failure must not look like success. |
| 356 | const failureInfo: RefreshFailureInfo = { timestamp: Date.now(), trigger, errorMessage }; |
| 357 | |
| 358 | finalizeInFlight(); |
| 359 | |
| 360 | try { |
| 361 | this.onRefreshError?.(failureInfo); |
| 362 | } catch (callbackError) { |
| 363 | console.error("[RefreshController] onRefreshError callback threw", callbackError); |
| 364 | } |
| 365 | }; |
| 366 | |
| 367 | // Unified promise pipeline: normalizes sync throws and async rejections |
| 368 | // into a single failure path so all errors are handled identically. |