fix(news): answer within the browser's wait on a first read

A country's first World News API read, or Liveuamap's, could take up to
20 s while the browser waits 15, so the panel showed the browser's own
"signal timed out". A read with nothing to serve yet is now waited for
at most 12 s, as GDELT's already was: past that the section answers
unavailable while the read goes on, and the next refresh is served from
it. A client timeout reads "News feed unavailable".
This commit is contained in:
Denis Groshev committed 2026-11-06 11:41:37 +01:00
1 parent 525543d08a
commit c6ae4c44d7
6 files changed
+87 -25

No files matched your search

+19 -7
View File
@@ -161,13 +161,25 @@ export function createNewsSource({
signal?.throwIfAborted();
const timeout = AbortSignal.timeout(timeoutMs);
const request = signal ? AbortSignal.any([signal, timeout]) : timeout;
const response = await fetchImpl(`${BASE_PATH}${path}`, {
signal: request,
cache: 'no-store',
redirect: 'error',
});
if (!response.ok) throw await responseError(response);
return readResponseJsonCapped(response, MAX_BYTES, request);
try {
const response = await fetchImpl(`${BASE_PATH}${path}`, {
signal: request,
cache: 'no-store',
redirect: 'error',
});
if (!response.ok) throw await responseError(response);
return await readResponseJsonCapped(response, MAX_BYTES, request);
} catch (cause) {
// Out of time the feed reads unavailable, not the browser's own words
// ("signal timed out"); the caller's own abort stays an abort.
if (timeout.aborted && !signal?.aborted)
throw Object.assign(new Error(msg('News feed unavailable')), {
code: 'timeout',
unavailable: false,
retryable: true,
});
throw cause;
}
}
return {
// `hours` asks for counts over a shorter window than the server's own.
+6 -1
View File
@@ -176,7 +176,12 @@ test('reads time out and follow the caller signal', async () => {
);
});
const slow = createNewsSource({ fetchImpl: hang, timeoutMs: 20 });
await assert.rejects(slow.getHotspots(), { name: 'TimeoutError' });
// Out of time it reads unavailable, in the app's words, and is retried.
await assert.rejects(slow.getHotspots(), {
message: 'News feed unavailable',
code: 'timeout',
retryable: true,
});
const caller = new AbortController();
const pending = createNewsSource({ fetchImpl: hang }).getFeed(
{},