From eecaa48ec262627266536492b9ac93ba5a616f06 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 31 May 2026 22:34:28 +0800 Subject: [PATCH] Limit city detail fallback to batch failures --- .../__tests__/refreshCadencePolicy.test.ts | 6 +++ .../scan-terminal/temperature-chart-logic.ts | 49 +++++++++++-------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts index 8bb15b76..9932b70e 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts @@ -118,6 +118,12 @@ export async function runTests() { flushCityDetailBatchBlock.includes("payload?.partial === true"), "partial detail-batch misses should resolve without immediately issuing single-city fallback requests", ); + assert( + flushCityDetailBatchBlock.includes("if (!payload)") && + flushCityDetailBatchBlock.includes("resolveCityDetailBatchWithSingleFallback") && + flushCityDetailBatchBlock.includes("resolveBatchWaiters(waiters, null)"), + "single-city detail fallback should be reserved for whole-batch failures rather than successful batch misses", + ); const fetchHourlyBlock = chartLogicSource.match(/async function fetchHourlyForecastForCity[\s\S]*?\r?\n}\r?\n\r?\nfunction fetchCityDetailWithTimeout/)?.[0] || ""; assert( fetchHourlyBlock.includes("queueCityDetailBatch(city, resParam)") && diff --git a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts index edd81b27..eda3dbd4 100644 --- a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts +++ b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts @@ -1118,6 +1118,11 @@ async function flushCityDetailBatch(resolution: string) { try { const payload = await fetchCityDetailBatchWithTimeout(cities, resolution); + if (!payload) { + await resolveCityDetailBatchWithSingleFallback(cities, queue, resolution); + return; + } + const details = payload?.details || {}; const partialMissingCities = payload?.partial === true @@ -1136,33 +1141,35 @@ async function flushCityDetailBatch(resolution: string) { resolveBatchWaiters(waiters, null); return; } - try { - resolveBatchWaiters( - waiters, - await runQueuedHourlyDetailRequest(() => fetchSingleHourlyForecastForCity(city, resolution)), - ); - } catch (error) { - rejectBatchWaiters(waiters, error); - } + resolveBatchWaiters(waiters, null); }), ); } catch (error) { - await Promise.all( - cities.map(async (city) => { - const waiters = queue.waiters.get(city); - try { - resolveBatchWaiters( - waiters, - await runQueuedHourlyDetailRequest(() => fetchSingleHourlyForecastForCity(city, resolution)), - ); - } catch (fallbackError) { - rejectBatchWaiters(waiters, fallbackError || error); - } - }), - ); + await resolveCityDetailBatchWithSingleFallback(cities, queue, resolution, error); } } +async function resolveCityDetailBatchWithSingleFallback( + cities: string[], + queue: CityDetailBatchQueue, + resolution: string, + reason?: unknown, +) { + await Promise.all( + cities.map(async (city) => { + const waiters = queue.waiters.get(city); + try { + resolveBatchWaiters( + waiters, + await runQueuedHourlyDetailRequest(() => fetchSingleHourlyForecastForCity(city, resolution)), + ); + } catch (fallbackError) { + rejectBatchWaiters(waiters, fallbackError || reason); + } + }), + ); +} + function fetchCityDetailBatchWithTimeout(cities: string[], resolution: string) { const controller = new AbortController(); const timeoutId = globalThis.setTimeout(() => controller.abort(), HOURLY_DETAIL_REQUEST_TIMEOUT_MS);