fix: improved js error handling
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
Reference in New Issue
Block a user