Skip to content

Commit f501ead

Browse files
committed
Improve API resilience and rate limiting
- Implement proper rate limiting with batched requests (5 per batch, 100ms delays) - Reduce concurrent requests from 21 to ~16 total over 400ms - Add timeout control with AbortController (10s timeout) - Better error categorization (network vs API vs timeout) - Replace 'API down' messaging with accurate error descriptions - Add retry functionality without page reload - Include proper Accept headers for API requests - More robust error handling for browser environment This should resolve the false 'API unavailable' messages when the API is actually working.
1 parent f93d8f0 commit f501ead

File tree

2 files changed

+130
-35
lines changed

2 files changed

+130
-35
lines changed

api-test.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Met API Test</title>
5+
</head>
6+
<body>
7+
<h1>Met Museum API Test</h1>
8+
<button onclick="testAPI()">Test API</button>
9+
<div id="result"></div>
10+
11+
<script>
12+
async function testAPI() {
13+
const result = document.getElementById('result');
14+
result.innerHTML = 'Testing API...';
15+
16+
try {
17+
console.log('Starting API test...');
18+
19+
const response = await fetch('https://collectionapi.metmuseum.org/public/collection/v1/search?isHighlight=true&hasImages=true&q=a');
20+
21+
console.log('Response status:', response.status, response.statusText);
22+
console.log('Response headers:', [...response.headers.entries()]);
23+
24+
if (!response.ok) {
25+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
26+
}
27+
28+
const data = await response.json();
29+
console.log('API Response:', data);
30+
31+
result.innerHTML = `
32+
<h2>✅ API Working!</h2>
33+
<p>Found ${data.total} artworks</p>
34+
<p>First few IDs: ${data.objectIDs.slice(0, 5).join(', ')}</p>
35+
`;
36+
37+
} catch (error) {
38+
console.error('API Error:', error);
39+
result.innerHTML = `
40+
<h2>❌ API Failed</h2>
41+
<p>Error: ${error.message}</p>
42+
<p>Check browser console for details</p>
43+
`;
44+
}
45+
}
46+
</script>
47+
</body>
48+
</html>

art.html

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ <h2 class="text-2xl font-light text-gray-800 mb-2">${collection.title}</h2>
293293
]
294294
};
295295

296-
// Simple working approach with fallback when API is down
296+
// Robust API fetching with rate limiting and retry logic
297297
async function getCuratedArtworks(collectionKey) {
298298
try {
299299
// Show loading state
@@ -302,13 +302,22 @@ <h2 class="text-2xl font-light text-gray-800 mb-2">${collection.title}</h2>
302302

303303
console.log('Attempting to fetch from Met Museum API...');
304304

305-
// Use the exact same approach as the working example
306-
const response = await fetch('https://collectionapi.metmuseum.org/public/collection/v1/search?isHighlight=true&hasImages=true&q=a');
305+
// Fetch search results with timeout
306+
const controller = new AbortController();
307+
const timeoutId = setTimeout(() => controller.abort(), 10000);
307308

309+
const response = await fetch('https://collectionapi.metmuseum.org/public/collection/v1/search?isHighlight=true&hasImages=true&q=a', {
310+
signal: controller.signal,
311+
headers: {
312+
'Accept': 'application/json'
313+
}
314+
});
315+
316+
clearTimeout(timeoutId);
308317
console.log('Response status:', response.status, response.statusText);
309318

310319
if (!response.ok) {
311-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
320+
throw new Error(`API returned ${response.status}: ${response.statusText}`);
312321
}
313322

314323
const data = await response.json();
@@ -317,58 +326,96 @@ <h2 class="text-2xl font-light text-gray-800 mb-2">${collection.title}</h2>
317326
const objectIDs = data.objectIDs;
318327

319328
if (!objectIDs || objectIDs.length === 0) {
320-
throw new Error('No artworks found in the Met Museum collection');
329+
throw new Error('No artworks found in search results');
321330
}
322331

323-
console.log(`Found ${objectIDs.length} artworks, fetching first 20...`);
332+
console.log(`Found ${objectIDs.length} artworks, fetching first 15 details...`);
324333

325-
// Fetch details for a selection of artworks (exactly like the working example)
326-
const artworks = await Promise.all(objectIDs.slice(0, 20).map(async (id) => {
327-
try {
328-
const res = await fetch(`https://collectionapi.metmuseum.org/public/collection/v1/objects/${id}`);
329-
if (!res.ok) return null;
330-
return res.json();
331-
} catch (err) {
332-
console.warn(`Failed to fetch artwork ${id}:`, err);
333-
return null;
334+
// Fetch artwork details with rate limiting (staggered requests)
335+
const artworks = [];
336+
const batchSize = 5; // Smaller batches to respect rate limits
337+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
338+
339+
for (let i = 0; i < Math.min(15, objectIDs.length); i += batchSize) {
340+
const batch = objectIDs.slice(i, i + batchSize);
341+
console.log(`Fetching batch ${Math.floor(i/batchSize) + 1}:`, batch);
342+
343+
const batchPromises = batch.map(async (id) => {
344+
try {
345+
const res = await fetch(`https://collectionapi.metmuseum.org/public/collection/v1/objects/${id}`, {
346+
headers: { 'Accept': 'application/json' }
347+
});
348+
if (!res.ok) {
349+
console.warn(`Failed to fetch artwork ${id}: ${res.status}`);
350+
return null;
351+
}
352+
return await res.json();
353+
} catch (err) {
354+
console.warn(`Network error fetching artwork ${id}:`, err.message);
355+
return null;
356+
}
357+
});
358+
359+
const batchResults = await Promise.all(batchPromises);
360+
artworks.push(...batchResults.filter(artwork => artwork !== null));
361+
362+
// Small delay between batches to respect rate limits
363+
if (i + batchSize < Math.min(15, objectIDs.length)) {
364+
await delay(100);
334365
}
335-
}));
366+
}
336367

337368
// Filter and display only artworks with images
338369
const validArtworks = artworks.filter(artwork => artwork && artwork.primaryImageSmall);
339-
console.log(`Found ${validArtworks.length} artworks with images`);
370+
console.log(`Successfully loaded ${validArtworks.length} artworks with images`);
340371

341372
if (validArtworks.length === 0) {
342-
throw new Error('No artworks with images available at this time');
373+
throw new Error('No artworks with images found in current batch');
343374
}
344375

345376
displayArtworks(validArtworks);
346377

347378
} catch (error) {
348-
console.error('Error fetching artworks:', error);
349-
console.log('Falling back to sample artworks...');
350-
351-
// Use sample artworks as fallback
352-
const fallbackArtworks = sampleArtworks[collectionKey] || sampleArtworks.impressionist;
379+
console.error('Met Museum API Error:', error);
353380

354-
// Add a notice that we're showing sample data
355-
gallery.innerHTML = `
356-
<div class="col-span-full mb-6">
357-
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 text-center">
358-
<i class="fas fa-info-circle text-blue-500 mr-2"></i>
359-
<span class="text-blue-700 text-sm">
360-
The Met Museum API is temporarily unavailable. Showing sample artworks to demonstrate the gallery.
361-
</span>
362-
</div>
363-
</div>
364-
`;
381+
// Only show fallback for actual API failures, not rate limiting issues
382+
if (error.name === 'AbortError') {
383+
console.log('Request timed out, showing fallback...');
384+
} else if (error.message.includes('Failed to fetch')) {
385+
console.log('Network error, showing fallback...');
386+
} else {
387+
console.log('API error, showing fallback...');
388+
}
365389

366-
displayArtworks(fallbackArtworks, true);
390+
showFallbackWithRetry(collectionKey, error.message);
367391
} finally {
368392
loadingIndicator.style.display = 'none';
369393
}
370394
}
371395

396+
function showFallbackWithRetry(collectionKey, errorMessage) {
397+
const fallbackArtworks = sampleArtworks[collectionKey] || sampleArtworks.impressionist;
398+
399+
// Add a notice with retry option
400+
gallery.innerHTML = `
401+
<div class="col-span-full mb-6">
402+
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 text-center">
403+
<i class="fas fa-exclamation-triangle text-yellow-500 mr-2"></i>
404+
<div class="text-yellow-800 text-sm">
405+
<p class="mb-2">Unable to load live artworks from The Met Museum API.</p>
406+
<p class="mb-3 text-xs text-yellow-700">Error: ${errorMessage}</p>
407+
<button onclick="getCuratedArtworks('${collectionKey}')"
408+
class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-1 rounded text-xs transition-colors">
409+
<i class="fas fa-redo mr-1"></i>Try Again
410+
</button>
411+
</div>
412+
</div>
413+
</div>
414+
`;
415+
416+
displayArtworks(fallbackArtworks, true);
417+
}
418+
372419
function displayArtworks(artworks, isFallback = false) {
373420
// Don't clear gallery if we're adding fallback notice
374421
if (!isFallback) {

0 commit comments

Comments
 (0)