From 1e99e11e58012e04c87985b72bec89dae2efad8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E7=8F=AD=E4=B8=83=E5=8F=B7?= <9159450+luban-71@user.noreply.gitee.com> Date: Tue, 1 Sep 2026 18:33:28 +0800 Subject: [PATCH] fix: add isFulfilled guard and use it at all Promise.allSettled sites (#446) - Add isFulfilled() type guard to lib/safe-operations.ts - Replace inline r && r.status === 'fulfilled' checks in dashboard/page.tsx - Narrows correctly under noUncheckedIndexedAccess Closes #446 --- app/dashboard/page.tsx | 6 +++--- lib/safe-operations.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index a8d1509..34078a6 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -13,6 +13,7 @@ import { fromStroops } from "@/lib/format"; import { refreshStreamData } from "@/lib/queryClient"; import { useNetworkStatus } from "@/hooks/useNetworkStatus"; import type { StreamInfo } from "@/lib/stream"; +import { isFulfilled } from "@/lib/safe-operations"; type Tab = "receiving" | "sending"; type StreamStatus = "active" | "paused" | "ended" | "cancelled"; @@ -67,7 +68,7 @@ async function loadRows( for (let i = 0; i < uniqueIds.length; i++) { const r = addrResults[i]; if (signal.aborted) return { rows: [], failedCount: 0 }; - if (r && r.status === "fulfilled" && r.value && typeof r.value === "string") { + if (isFulfilled(r) && r.value && typeof r.value === "string") { addrPairs.push({ id: uniqueIds[i]!, rowId: uniqueIds[i]!.toString(), addr: r.value }); } else { failedCount++; @@ -93,8 +94,7 @@ async function loadRows( const r = results[j]; const pair = batch[j]!; if ( - r && - r.status === "fulfilled" && + isFulfilled(r) && r.value[0] && typeof r.value[0] === "object" && typeof r.value[0].ratePerSecond === "bigint" diff --git a/lib/safe-operations.ts b/lib/safe-operations.ts index 5349b64..2a9225c 100644 --- a/lib/safe-operations.ts +++ b/lib/safe-operations.ts @@ -329,3 +329,17 @@ export function clearIdempotencyKeys(): void { activeIdempotencyKeys.clear(); } + +// ── Promise.allSettled Helpers ─────────────────────────────────────────────── + +/** + * Type guard for a fulfilled Promise.allSettled result. + * + * Use this instead of inline `r && r.status === 'fulfilled'` so + * TypeScript narrows the value correctly under `noUncheckedIndexedAccess`. + */ +export function isFulfilled( + result: PromiseSettledResult | undefined, +): result is PromiseFulfilledResult { + return result !== undefined && result.status === 'fulfilled'; +}