修复图表观测缓存阻塞详情加载

This commit is contained in:
2569718930@qq.com
2026-06-16 21:58:14 +08:00
parent 4627a110bf
commit d3e76ecac9
2 changed files with 46 additions and 7 deletions
@@ -7,6 +7,7 @@ import {
mergeHourlyWithLiveObservations, mergeHourlyWithLiveObservations,
mergePatchIntoHourly, mergePatchIntoHourly,
mergeRowObservationIntoHourly, mergeRowObservationIntoHourly,
readCachedHourlyForInitialRow,
rememberHourlyDetailSnapshot, rememberHourlyDetailSnapshot,
selectInitialHourlyForRowChange, selectInitialHourlyForRowChange,
seedHourlyForecastFromRow, seedHourlyForecastFromRow,
@@ -223,6 +224,28 @@ export function runTests() {
"chart should use the current scan row local date when cached detail localDate is older so today's model curve is drawn through 23:00", "chart should use the current scan row local date when cached detail localDate is older so today's model curve is drawn through 23:00",
); );
_hourlyCache.clear();
_hourlyCache.set("madrid:10m", {
ts: Date.now(),
data: {
localDate: "2026-06-16",
localTime: "15:46",
times: [],
temps: [],
airportPrimary: {
temp: 28.8,
obs_time: "2026-06-16T13:46:00Z",
},
airportPrimaryTodayObs: [["2026-06-16T13:46:00Z", 28.8]],
} as any,
});
const observationOnlyInitialCache = readCachedHourlyForInitialRow("madrid", "10m");
assert(
observationOnlyInitialCache === null,
"observation-only hourly cache entries must not block loading the full city detail payload",
);
_hourlyCache.clear();
const correctedDetailChart = getTemperatureChartData( const correctedDetailChart = getTemperatureChartData(
{ {
name: "shanghai", name: "shanghai",
@@ -441,9 +441,13 @@ function isRetainedHourlyCacheEntry(
return Number.isFinite(age) && age >= 0 && age < maxAgeMs; return Number.isFinite(age) && age >= 0 && age < maxAgeMs;
} }
function isUsableHourlyDetailCacheEntry(entry: HourlyCacheEntry | null | undefined) {
return Boolean(entry?.data && hasFullHourlyDetailPayload(entry.data));
}
function pruneHourlyCache() { function pruneHourlyCache() {
for (const [key, entry] of _hourlyCache.entries()) { for (const [key, entry] of _hourlyCache.entries()) {
if (!isRetainedHourlyCacheEntry(entry, HOURLY_CACHE_STALE_TTL_MS)) { if (!isUsableHourlyDetailCacheEntry(entry) || !isRetainedHourlyCacheEntry(entry, HOURLY_CACHE_STALE_TTL_MS)) {
_hourlyCache.delete(key); _hourlyCache.delete(key);
} }
} }
@@ -459,7 +463,7 @@ function pruneHourlyCache() {
} }
function rememberMemoryHourlyCacheEntry(cacheKey: string, entry: HourlyCacheEntry) { function rememberMemoryHourlyCacheEntry(cacheKey: string, entry: HourlyCacheEntry) {
if (!cacheKey || !entry?.data) return; if (!cacheKey || !isUsableHourlyDetailCacheEntry(entry)) return;
_hourlyCache.set(cacheKey, entry); _hourlyCache.set(cacheKey, entry);
pruneHourlyCache(); pruneHourlyCache();
} }
@@ -476,7 +480,11 @@ function readSessionCache(
const maxAgeMs = options.allowStale const maxAgeMs = options.allowStale
? HOURLY_CACHE_STALE_TTL_MS ? HOURLY_CACHE_STALE_TTL_MS
: options.maxAgeMs ?? SESSION_CACHE_TTL_MS; : options.maxAgeMs ?? SESSION_CACHE_TTL_MS;
if (item && item.ts && isRetainedHourlyCacheEntry(item, maxAgeMs)) { if (!item || !item.ts || !isUsableHourlyDetailCacheEntry(item) || !isRetainedHourlyCacheEntry(item, HOURLY_CACHE_STALE_TTL_MS)) {
sessionStorage.removeItem(`${SESSION_CACHE_PREFIX}${city}`);
return null;
}
if (isRetainedHourlyCacheEntry(item, maxAgeMs)) {
return item; return item;
} }
} catch {} } catch {}
@@ -488,10 +496,14 @@ function readHourlyCacheEntry(
options: { allowStale?: boolean; maxAgeMs?: number } = {}, options: { allowStale?: boolean; maxAgeMs?: number } = {},
): HourlyCacheEntry | null { ): HourlyCacheEntry | null {
const cached = _hourlyCache.get(cacheKey); const cached = _hourlyCache.get(cacheKey);
if (cached && (options.allowStale ? isRetainedHourlyCacheEntry(cached) : isFreshHourlyCacheEntry(cached, options.maxAgeMs))) { if (
cached &&
isUsableHourlyDetailCacheEntry(cached) &&
(options.allowStale ? isRetainedHourlyCacheEntry(cached) : isFreshHourlyCacheEntry(cached, options.maxAgeMs))
) {
return cached; return cached;
} }
if (cached && !isRetainedHourlyCacheEntry(cached)) { if (cached && (!isUsableHourlyDetailCacheEntry(cached) || !isRetainedHourlyCacheEntry(cached))) {
_hourlyCache.delete(cacheKey); _hourlyCache.delete(cacheKey);
} }
@@ -509,10 +521,14 @@ function readHourlyCacheSnapshot(
options: { allowStale?: boolean; maxAgeMs?: number } = {}, options: { allowStale?: boolean; maxAgeMs?: number } = {},
): HourlyDetailSnapshotEntry | null { ): HourlyDetailSnapshotEntry | null {
const cached = _hourlyCache.get(cacheKey); const cached = _hourlyCache.get(cacheKey);
if (cached && (options.allowStale ? isRetainedHourlyCacheEntry(cached) : isFreshHourlyCacheEntry(cached, options.maxAgeMs))) { if (
cached &&
isUsableHourlyDetailCacheEntry(cached) &&
(options.allowStale ? isRetainedHourlyCacheEntry(cached) : isFreshHourlyCacheEntry(cached, options.maxAgeMs))
) {
return { ...cached, source: "memory_cache" }; return { ...cached, source: "memory_cache" };
} }
if (cached && !isRetainedHourlyCacheEntry(cached)) { if (cached && (!isUsableHourlyDetailCacheEntry(cached) || !isRetainedHourlyCacheEntry(cached))) {
_hourlyCache.delete(cacheKey); _hourlyCache.delete(cacheKey);
} }