fix: ignore stale data center requests

This commit is contained in:
ZhuLinsen
2026-08-30 21:15:14 +08:00
parent 1b25634e16
commit 95887b5ed5
2 changed files with 51 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
import type React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Database, RefreshCw, Settings2, TriangleAlert } from 'lucide-react';
import { Link } from 'react-router-dom';
import { dataCapabilityApi } from '../api/dataCapability';
@@ -54,26 +54,34 @@ const DataCenterPage: React.FC = () => {
const [overview, setOverview] = useState<DataCapabilityOverview | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const requestGeneration = useRef(0);
useEffect(() => {
document.title = `${t('dataCenter.title')} - DSA`;
}, [t]);
const load = useCallback(async () => {
const generation = ++requestGeneration.current;
setLoading(true);
setError('');
try {
setOverview(await dataCapabilityApi.getOverview());
const nextOverview = await dataCapabilityApi.getOverview();
if (generation !== requestGeneration.current) return;
setOverview(nextOverview);
} catch {
if (generation !== requestGeneration.current) return;
setOverview(null);
setError(t('dataCenter.loadError'));
} finally {
setLoading(false);
if (generation === requestGeneration.current) setLoading(false);
}
}, [t]);
useEffect(() => {
void load();
return () => {
requestGeneration.current += 1;
};
}, [load]);
const summary = useMemo(() => {

View File

@@ -1,6 +1,7 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { UiLanguageProvider, useUiLanguage } from '../../contexts/UiLanguageContext';
import DataCenterPage from '../DataCenterPage';
const { getOverview } = vi.hoisted(() => ({ getOverview: vi.fn() }));
@@ -93,6 +94,21 @@ const overview = {
warnings: ['screening_health_unknown'],
};
function LanguageSwitch() {
const { language, setLanguage } = useUiLanguage();
return <button onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}>switch language</button>;
}
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});
return { promise, resolve, reject };
}
beforeEach(() => {
vi.clearAllMocks();
getOverview.mockResolvedValue(overview);
@@ -137,4 +153,28 @@ describe('DataCenterPage', () => {
expect(await screen.findByText('暂无能力数据')).toBeInTheDocument();
await waitFor(() => expect(getOverview).toHaveBeenCalledTimes(2));
});
it('ignores an older request that finishes after a language-triggered reload', async () => {
const first = deferred<typeof overview>();
const second = deferred<typeof overview>();
getOverview.mockReset().mockReturnValueOnce(first.promise).mockReturnValueOnce(second.promise);
render(
<UiLanguageProvider>
<LanguageSwitch />
<MemoryRouter><DataCenterPage /></MemoryRouter>
</UiLanguageProvider>,
);
await waitFor(() => expect(getOverview).toHaveBeenCalledTimes(1));
fireEvent.click(screen.getByRole('button', { name: 'switch language' }));
await waitFor(() => expect(getOverview).toHaveBeenCalledTimes(2));
second.resolve(overview);
expect(await screen.findByText('AkShare')).toBeInTheDocument();
first.reject(new Error('stale failure'));
await waitFor(() => expect(screen.queryByText('Failed to load data overview')).not.toBeInTheDocument());
expect(screen.getByText('AkShare')).toBeInTheDocument();
});
});