Fix Overpass timeouts, cancellation and endpoint priority
This commit is contained in:
1 parent
2d4bad0dc7
commit
9487bc23bd
4 files changed
+136
-7
No files matched your search
+40
-7
@@ -15,14 +15,32 @@ export interface OsmElement {
|
||||
}
|
||||
|
||||
export const OVERPASS_ENDPOINTS = [
|
||||
'https://maps.mail.ru/osm/tools/overpass/api/interpreter',
|
||||
'https://overpass-api.de/api/interpreter',
|
||||
'https://overpass.private.coffee/api/interpreter',
|
||||
'https://maps.mail.ru/osm/tools/overpass/api/interpreter',
|
||||
];
|
||||
|
||||
const REQUEST_TIMEOUT_MS = 45_000;
|
||||
|
||||
function waitForRetry(ms: number, signal?: AbortSignal): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cancel = () => {
|
||||
clearTimeout(timer);
|
||||
signal?.removeEventListener('abort', cancel);
|
||||
reject(new DOMException('Отменено', 'AbortError'));
|
||||
};
|
||||
const timer = setTimeout(() => {
|
||||
signal?.removeEventListener('abort', cancel);
|
||||
resolve();
|
||||
}, ms);
|
||||
signal?.addEventListener('abort', cancel, { once: true });
|
||||
if (signal?.aborted) cancel();
|
||||
});
|
||||
}
|
||||
|
||||
export function buildQuery(b: BBox): string {
|
||||
const bb = `(${b[0]},${b[1]},${b[2]},${b[3]})`;
|
||||
return `[out:json][timeout:120];
|
||||
return `[out:json][timeout:30];
|
||||
(
|
||||
way["building"]${bb};
|
||||
relation["building"]["type"="multipolygon"]${bb};
|
||||
@@ -56,27 +74,42 @@ export async function fetchOverpass(b: BBox, onStatus?: (s: string) => void, sig
|
||||
let lastErr: unknown = null;
|
||||
for (const url of OVERPASS_ENDPOINTS) {
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
if (signal?.aborted) throw new DOMException('Отменено', 'AbortError');
|
||||
const controller = new AbortController();
|
||||
const cancel = () => controller.abort();
|
||||
signal?.addEventListener('abort', cancel, { once: true });
|
||||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
controller.abort();
|
||||
}, REQUEST_TIMEOUT_MS);
|
||||
try {
|
||||
onStatus?.(`Запрос к ${new URL(url).host}${attempt ? ' (повтор)' : ''}…`);
|
||||
onStatus?.(`Запрос к ${new URL(url).host}${attempt ? ' (повтор)' : ''} — до 45 секунд…`);
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: 'data=' + encodeURIComponent(query),
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
signal,
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (res.status === 429 || res.status === 504) {
|
||||
lastErr = new Error(`Сервер перегружен (${res.status})`);
|
||||
await new Promise((r) => setTimeout(r, 3000 * (attempt + 1)));
|
||||
clearTimeout(timer);
|
||||
if (attempt === 0) await waitForRetry(3000, signal);
|
||||
continue;
|
||||
}
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = await res.json();
|
||||
if (signal?.aborted) throw new DOMException('Отменено', 'AbortError');
|
||||
if (json.remark && /error|timed out|runtime/i.test(json.remark)) throw new Error(json.remark);
|
||||
if (!Array.isArray(json.elements)) throw new Error('Сервер вернул ответ без списка elements');
|
||||
return json.elements as OsmElement[];
|
||||
} catch (e) {
|
||||
if (signal?.aborted) throw e;
|
||||
lastErr = e;
|
||||
if (signal?.aborted) throw new DOMException('Отменено', 'AbortError');
|
||||
lastErr = timedOut ? new Error(`Таймаут ${new URL(url).host}: 45 секунд`) : e;
|
||||
break;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
signal?.removeEventListener('abort', cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user