| 109 | const classUrl = `${SOILGRIDS_BASE}/classification/query?lon=${lon}&lat=${lat}&number_classes=3`; |
| 110 | |
| 111 | async function fetchWithRetry(url: string): Promise<Response> { |
| 112 | const delays = [500, 1500]; |
| 113 | let last: Response | null = null; |
| 114 | for (let i = 0; i <= delays.length; i++) { |
| 115 | try { |
| 116 | const r = await fetch(url, { signal: AbortSignal.timeout(8000) }); |
| 117 | if (r.ok) return r; |
| 118 | last = r; |
| 119 | if (r.status >= 500 && i < delays.length) { |
| 120 | await new Promise((res) => setTimeout(res, delays[i] + Math.floor(Math.random() * 250))); |
| 121 | continue; |
| 122 | } |
| 123 | return r; |
| 124 | } catch (err) { |
| 125 | console.error("SoilGrids fetch failed:", (err as Error)?.message); |
| 126 | if (i < delays.length) { |
| 127 | await new Promise((res) => setTimeout(res, delays[i])); |
| 128 | continue; |
| 129 | } |
| 130 | // Synthesize a 503 so caller's fallback path triggers. |
| 131 | return new Response("upstream timeout", { status: 503 }); |
| 132 | } |
| 133 | } |
| 134 | return last as Response; |
| 135 | } |
| 136 | |
| 137 | const [propsRes, classRes] = await Promise.all([ |
| 138 | fetchWithRetry(propsUrl), |