Public Access
1
0

fix: improved js error handling

This commit is contained in:
2025-08-28 11:31:56 -04:00
parent cefcdf4e39
commit d11177b51e
2 changed files with 83 additions and 65 deletions

View File

@@ -1,3 +1,30 @@
/**
* Helper function to handle fetch requests, parse the response, and manage common errors.
* @param {string} url - The URL to fetch.
* @param {object} options - The options for the fetch request.
* @returns {Promise<object>} - A promise that resolves to the JSON response data.
*/
const fetchApiData = async (url, options = {}) => {
const response = await fetch(url, options);
if (!response.ok) {
let errorMessage;
// Specifically handle the "Too Many Requests" error
if (response.status === 429) {
// Try to get the plain text message from the response body
const errorText = await response.text();
errorMessage = errorText || 'Too many requests. Please try again later.';
} else {
errorMessage = `Server responded with status ${response.status}`;
}
// Throw an error with our improved message
throw new Error(errorMessage);
}
// If the response is OK, try to parse it as JSON.
return response.json();
};
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const likeIcon = document.querySelector('[data-like-button]'); const likeIcon = document.querySelector('[data-like-button]');
if (!likeIcon) return; if (!likeIcon) return;
@@ -28,9 +55,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Fetch and display the initial like count and set the initial state // Fetch and display the initial like count and set the initial state
const getInitialState = async () => { const getInitialState = async () => {
try { try {
const response = await fetch(`/api/likes/${slug}`); const data = await fetchApiData(`/api/likes/${slug}`);
if (!response.ok) return;
const data = await response.json();
const isLiked = localStorage.getItem(storageKey) === 'true'; const isLiked = localStorage.getItem(storageKey) === 'true';
updateUI(isLiked, data.count || 0); updateUI(isLiked, data.count || 0);
// Check if the user has ever interacted with this button before // Check if the user has ever interacted with this button before
@@ -43,7 +68,7 @@ document.addEventListener('DOMContentLoaded', () => {
}, 2000); // Animation duration is 2s }, 2000); // Animation duration is 2s
} }
} catch (error) { } catch (error) {
console.error('Error fetching likes:', error); console.error(`Error fetching initial like state for slug ${slug}:`, error.message);
} }
}; };
@@ -62,20 +87,15 @@ document.addEventListener('DOMContentLoaded', () => {
const method = isCurrentlyLiked ? 'DELETE' : 'POST'; const method = isCurrentlyLiked ? 'DELETE' : 'POST';
try { try {
const response = await fetch(`/api/likes/${slug}`, { method }); const data = await fetchApiData(`/api/likes/${slug}`, { method });
if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
}
const data = await response.json();
const isNowLiked = !isCurrentlyLiked; const isNowLiked = !isCurrentlyLiked;
// Update localStorage to remember the user's choice // Update localStorage to remember the user's choice
localStorage.setItem(storageKey, isNowLiked); localStorage.setItem(storageKey, isNowLiked);
// Update the button and count on the page // Update the button and count on the page
updateUI(isNowLiked, data.count); updateUI(isNowLiked, data.count);
} catch (error) { } catch (error) {
console.error('Error submitting like/unlike:', error); console.error(`Error submitting like/unlike for slug ${slug}:`, error.message);
} finally { } finally {
isRequestInProgress = false; isRequestInProgress = false;
} }

View File

@@ -1,72 +1,70 @@
/**
* Helper function to handle fetch requests, parse the response, and manage common errors.
* @param {string} url - The URL to fetch.
* @param {object} options - The options for the fetch request.
* @returns {Promise<object>} - A promise that resolves to the JSON response data.
*/
const fetchViewData = async (url, options = {}) => {
const response = await fetch(url, options);
if (!response.ok) {
let errorMessage;
// Specifically handle the "Too Many Requests" error
if (response.status === 429) {
// Try to get the plain text message from the response body
const errorText = await response.text();
errorMessage = errorText || 'Too many requests. Please try again later.';
} else {
errorMessage = `Server responded with status ${response.status}`;
}
// Throw an error with our improved message
throw new Error(errorMessage);
}
// If the response is OK, try to parse it as JSON.
// A SyntaxError here would indicate an API problem (e.g., returning non-JSON on a 200 OK).
return response.json();
};
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Select all elements that are designated to display view counts. // Select all elements that are designated to display view counts.
const viewCountElements = document.querySelectorAll('[data-view-count]'); const viewCountElements = document.querySelectorAll('[data-view-count]');
// If no such elements are found, do nothing. // If no such elements are found, do nothing.
if (viewCountElements.length === 0) { if (viewCountElements.length === 0) return;
return;
}
// Process each view count element found on the page. // Process each view count element found on the page.
viewCountElements.forEach(element => { viewCountElements.forEach(async (element) => {
const slug = element.dataset.slug; const slug = element.dataset.slug;
if (!slug) { if (!slug) return;
// Skip if the element is missing the data-slug attribute.
return;
}
// Check if the element is on a single post page by looking for a `.post-meta` parent. // Check if the element is on a single post page by looking for a `.post-meta` parent.
// This distinguishes a single post view (which should be counted) from a list view. // This distinguishes a single post view (which should be counted) from a list view.
const isSinglePost = element.closest('.post-meta'); const isSinglePost = element.closest('.post-meta');
const shouldIncrement = isSinglePost && !localStorage.getItem(`viewed-${slug}`);
const apiUrl = `/api/views/${slug}`;
if (isSinglePost) { try {
// On a single post page, increment the view count. const options = shouldIncrement ? { method: 'POST' } : {};
const viewed = localStorage.getItem(`viewed-${slug}`); const data = await fetchViewData(apiUrl, options);
// Only increment if the user hasn't viewed this post in the current session. if (shouldIncrement) {
if (!viewed) { // Mark as viewed to prevent re-counting on refresh.
fetch(`/api/views/${slug}`, { method: 'POST' }) localStorage.setItem(`viewed-${slug}`, 'true');
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
const count = data.count ?? 'Error';
element.textContent = `${count} ${count === 1 ? 'view' : 'views'}`;
// Mark as viewed to prevent re-counting on refresh.
localStorage.setItem(`viewed-${slug}`, 'true');
})
.catch(error => {
console.error(`Error incrementing view count for slug ${slug}:`, error);
element.textContent = 'N/A';
});
} else {
// If already viewed in this session, just fetch the count without incrementing.
fetch(`/api/views/${slug}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
const count = data.count ?? 0;
element.textContent = `${count} ${count === 1 ? 'view' : 'views'}`;
})
.catch(error => {
console.error(`Error fetching view count for slug ${slug}:`, error);
element.textContent = 'N/A';
});
} }
} else {
// On a list page (like the main blog page), just fetch and display the count. // Update UI based on context (single post vs. list)
fetch(`/api/views/${slug}`) if (isSinglePost) {
.then(response => response.json()) // On a post, show "N views". Use 'Error' as a fallback if incrementing returns bad data.
.then(data => { const count = data.count ?? (shouldIncrement ? 'Error' : 0);
element.textContent = data.count ?? 0; element.textContent = `${count} ${count === 1 ? 'view' : 'views'}`;
}) } else {
.catch(error => { // On a list, just show the number.
console.error(`Error fetching view count for slug ${slug}:`, error); element.textContent = data.count ?? 0;
element.textContent = 'N/A'; }
}); } catch (error) {
console.error(`Error processing view count for slug ${slug}:`, error.message);
element.textContent = 'N/A';
} }
}); });
}); });