PR comments
- Account loading now dedupes in-flight requests per network instead of sharing one global promise across all networks. - Regression tests cover same-network reuse and cross-network isolation.
This commit is contained in:
@@ -21,6 +21,7 @@ import { Console } from '../utils/console';
|
||||
import { createSignInWindow, getReactState, setReactState } from '../requests/app';
|
||||
import { fetchNymPriceDeduped, getNetworkOverviewEndpoints, clearNymPriceCache } from '../api/networkOverview';
|
||||
import { signInAndNavigateToBalance } from '../utils/signInAndNavigateToBalance';
|
||||
import { dedupeInflightByKey } from '../utils/dedupeInflightByKey';
|
||||
import { toDisplay } from '../utils';
|
||||
|
||||
export const urls = (networkName?: Network) =>
|
||||
@@ -103,7 +104,7 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
const [loginType, setLoginType] = useState<'mnemonic' | 'password'>();
|
||||
const [isLoading, setIsLoadingInternal] = useState(false);
|
||||
const hadClientDetailsRef = useRef(false);
|
||||
const accountLoadInflightRef = useRef<Promise<Account | undefined> | null>(null);
|
||||
const accountLoadInflightRef = useRef<Map<Network, Promise<Account | undefined>>>(new Map());
|
||||
const [loadingPresentation, setLoadingPresentation] = useState<AppLoadingPresentation>('auth-splash');
|
||||
const [loadingOverlayTitle, setLoadingOverlayTitle] = useState('');
|
||||
const [loadingOverlaySubtitle, setLoadingOverlaySubtitle] = useState<string | undefined>();
|
||||
@@ -161,12 +162,8 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
setMixnodeDetails(null);
|
||||
};
|
||||
|
||||
const loadAccount = async (n: Network): Promise<Account | undefined> => {
|
||||
if (accountLoadInflightRef.current) {
|
||||
return accountLoadInflightRef.current;
|
||||
}
|
||||
|
||||
const pending = (async () => {
|
||||
const loadAccount = async (n: Network): Promise<Account | undefined> =>
|
||||
dedupeInflightByKey(accountLoadInflightRef.current, n, async () => {
|
||||
try {
|
||||
const client = await selectNetwork(n);
|
||||
setClientDetails(client);
|
||||
@@ -175,14 +172,8 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
enqueueSnackbar('Error loading account', { variant: 'error' });
|
||||
Console.error(e as string);
|
||||
return undefined;
|
||||
} finally {
|
||||
accountLoadInflightRef.current = null;
|
||||
}
|
||||
})();
|
||||
|
||||
accountLoadInflightRef.current = pending;
|
||||
return pending;
|
||||
};
|
||||
});
|
||||
|
||||
const loadStoredAccounts = async () => {
|
||||
const accounts = await listAccounts();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { dedupeInflightByKey } from './dedupeInflightByKey';
|
||||
|
||||
describe('dedupeInflightByKey', () => {
|
||||
it('reuses the in-flight promise for the same key', async () => {
|
||||
const inflight = new Map<string, Promise<string>>();
|
||||
let calls = 0;
|
||||
const load = () => {
|
||||
calls += 1;
|
||||
return new Promise<string>((resolve) => {
|
||||
setTimeout(() => resolve('done'), 20);
|
||||
});
|
||||
};
|
||||
|
||||
const first = dedupeInflightByKey(inflight, 'MAINNET', load);
|
||||
const second = dedupeInflightByKey(inflight, 'MAINNET', load);
|
||||
|
||||
expect(first).toBe(second);
|
||||
await first;
|
||||
expect(calls).toBe(1);
|
||||
});
|
||||
|
||||
it('does not reuse promises across different keys', async () => {
|
||||
const inflight = new Map<string, Promise<string>>();
|
||||
let calls = 0;
|
||||
const load = (value: string) => () => {
|
||||
calls += 1;
|
||||
return Promise.resolve(value);
|
||||
};
|
||||
|
||||
const mainnet = dedupeInflightByKey(inflight, 'MAINNET', load('mainnet'));
|
||||
const sandbox = dedupeInflightByKey(inflight, 'SANDBOX', load('sandbox'));
|
||||
|
||||
await expect(mainnet).resolves.toBe('mainnet');
|
||||
await expect(sandbox).resolves.toBe('sandbox');
|
||||
expect(calls).toBe(2);
|
||||
});
|
||||
|
||||
it('clears the key after the promise settles', async () => {
|
||||
const inflight = new Map<string, Promise<string>>();
|
||||
await dedupeInflightByKey(inflight, 'MAINNET', async () => 'done');
|
||||
expect(inflight.has('MAINNET')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
export function dedupeInflightByKey<K, T>(
|
||||
inflight: Map<K, Promise<T>>,
|
||||
key: K,
|
||||
load: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const pending = load().finally(() => {
|
||||
inflight.delete(key);
|
||||
});
|
||||
|
||||
inflight.set(key, pending);
|
||||
return pending;
|
||||
}
|
||||
Reference in New Issue
Block a user