Example 1: User List with Loading States
ตัวอย่างการดึงข้อมูล users จาก API พร้อม skeleton loading และ error handling
JavaScript Code:
// Load users from API
async function loadUsers() {
const container = document.getElementById('userListContainer');
// Show loading skeleton
container.innerHTML = createSkeletonCards(6);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
// Render user cards
container.innerHTML = users.map(user => `
<div class="card">
<div class="card-header">
<h3>${user.name}</h3>
</div>
<div class="card-body">
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>Phone:</strong> ${user.phone}</p>
<p><strong>Company:</strong> ${user.company.name}</p>
</div>
</div>
`).join('');
} catch (error) {
// Show error state
container.innerHTML = `
<div class="error-state">
<div class="icon-alert-circle"></div>
<h3>Failed to load users</h3>
<p>${error.message}</p>
<button onclick="loadUsers()" class="button primary">Retry</button>
</div>
`;
}
}
Example 2: Weather Widget with Auto-Refresh
Real-time weather data พร้อม loading spinner และ auto-refresh ทุก 30 วินาที
JavaScript Code:
// Weather widget with auto-refresh
let autoRefreshInterval = null;
async function loadWeather() {
const widget = document.getElementById('weatherWidget');
// Show loading spinner
widget.innerHTML = `
<div class="weather-loading">
<div class="spinner"></div>
<p>Loading weather data...</p>
</div>
`;
try {
// Using OpenWeatherMap API (requires API key)
const lat = 13.7563; // Bangkok
const lon = 100.5018;
const apiKey = 'demo'; // Replace with your API key
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`
);
if (!response.ok) throw new Error('Weather API failed');
const data = await response.json();
// Render weather data
widget.innerHTML = `
<div class="weather-content">
<h3>${data.name}</h3>
<div class="weather-temp">${Math.round(data.main.temp)}°C</div>
<p>${data.weather[0].description}</p>
<div class="weather-details">
<span>Humidity: ${data.main.humidity}%</span>
<span>Wind: ${data.wind.speed} m/s</span>
</div>
<small>Last updated: ${new Date().toLocaleTimeString()}</small>
</div>
`;
} catch (error) {
widget.innerHTML = `
<div class="error-state">
<p>Failed to load weather data</p>
<button onclick="loadWeather()" class="button">Retry</button>
</div>
`;
}
}
// Toggle auto-refresh
function toggleAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
} else {
autoRefreshInterval = setInterval(loadWeather, 30000);
}
}
Example 3: Product Search with Debounce
Search products พร้อม debounce และ loading indicator
Type to search products...
JavaScript Code:
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Search products
async function searchProducts(query) {
const resultsContainer = document.getElementById('productResults');
const loadingIndicator = document.getElementById('searchLoading');
if (!query.trim()) {
resultsContainer.innerHTML = '<p class="text-muted">Type to search products...</p>';
return;
}
// Show loading
loadingIndicator.style.display = 'block';
try {
const response = await fetch(
`https://dummyjson.com/products/search?q=${encodeURIComponent(query)}`
);
const data = await response.json();
// Hide loading
loadingIndicator.style.display = 'none';
if (data.products.length === 0) {
resultsContainer.innerHTML = '<p>No products found</p>';
return;
}
// Render results
resultsContainer.innerHTML = data.products.map(product => `
<div class="product-card">
<img src="${product.thumbnail}" alt="${product.title}">
<h4>${product.title}</h4>
<p class="price">$${product.price}</p>
<p class="rating">⭐ ${product.rating}</p>
</div>
`).join('');
} catch (error) {
loadingIndicator.style.display = 'none';
resultsContainer.innerHTML = `<p class="error">Search failed: ${error.message}</p>`;
}
}
// Attach debounced search to input
const debouncedSearch = debounce(searchProducts, 500);
document.getElementById('productSearch').addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Example 4: Form Submission (POST/PUT/DELETE)
ตัวอย่างการส่งข้อมูลไปยัง API พร้อม loading state และ success/error feedback
<div class="form-container">
<form id="userForm" class="form">
<div>
<label for="userName">Name:</label>
<span class="form-control">
<input type="text" id="userName" class="input" required>
</span>
</div>
<div>
<label for="userEmail">Email:</label>
<span class="form-control">
<input type="email" id="userEmail" class="input" required>
</span>
</div>
<div>
<label for="userJob">Job:</label>
<span class="form-control">
<input type="text" id="userJob" class="input" required>
</span>
</div>
<div class="form-actions">
<button type="submit" id="submitBtn" class="button primary">
<span class="btn-text">Create User</span>
<span class="btn-loading" style="display: none;">
<div class="spinner-small"></div> Creating...
</span>
</button>
<button type="button" id="updateBtn" class="button">Update User</button>
<button type="button" id="deleteBtn" class="button danger">Delete User</button>
</div>
</form>
<div id="formFeedback" class="feedback-container"></div>
</div>
// Create user (POST)
async function createUser(userData) {
const submitBtn = document.getElementById('submitBtn');
const btnText = submitBtn.querySelector('.btn-text');
const btnLoading = submitBtn.querySelector('.btn-loading');
// Show loading state
btnText.style.display = 'none';
btnLoading.style.display = 'inline-flex';
submitBtn.disabled = true;
try {
const response = await fetch('https://reqres.in/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error('Failed to create user');
const result = await response.json();
showFeedback('success', `User created successfully! ID: ${result.id}`);
document.getElementById('userForm').reset();
} catch (error) {
showFeedback('error', `Error: ${error.message}`);
} finally {
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
submitBtn.disabled = false;
}
}
// Update user (PUT)
async function updateUser(userId, userData) {
const updateBtn = document.getElementById('updateBtn');
updateBtn.disabled = true;
updateBtn.innerHTML = '<div class="spinner-small"></div> Updating...';
try {
const response = await fetch(`https://reqres.in/api/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error('Failed to update user');
const result = await response.json();
showFeedback('success', 'User updated successfully!');
} catch (error) {
showFeedback('error', `Error: ${error.message}`);
} finally {
updateBtn.disabled = false;
updateBtn.textContent = 'Update User';
}
}
// Delete user (DELETE)
async function deleteUser(userId) {
if (!confirm('Are you sure you want to delete this user?')) return;
const deleteBtn = document.getElementById('deleteBtn');
deleteBtn.disabled = true;
deleteBtn.innerHTML = '<div class="spinner-small"></div> Deleting...';
try {
const response = await fetch(`https://reqres.in/api/users/${userId}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete user');
showFeedback('success', 'User deleted successfully!');
document.getElementById('userForm').reset();
} catch (error) {
showFeedback('error', `Error: ${error.message}`);
} finally {
deleteBtn.disabled = false;
deleteBtn.textContent = 'Delete User';
}
}
Example 5: Multiple API Calls (Concurrent)
เรียก API หลายตัวพร้อมกัน และแสดง loading state แยกกันสำหรับแต่ละ section
Statistics
Recent Posts
Tasks
JavaScript Code:
// Load multiple APIs concurrently
async function loadDashboard() {
try {
// Show loading state for all cards
showLoadingState();
// Fetch all data concurrently
const [users, posts, todos] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/users').then(r => r.json()),
fetch('https://jsonplaceholder.typicode.com/posts?_limit=5').then(r => r.json()),
fetch('https://jsonplaceholder.typicode.com/todos?_limit=5').then(r => r.json())
]);
// Render statistics
document.getElementById('statsCard').innerHTML = `
<h3>Statistics</h3>
<div class="stats">
<div class="stat-item">
<span class="stat-value">${users.length}</span>
<span class="stat-label">Total Users</span>
</div>
<div class="stat-item">
<span class="stat-value">${posts.length}</span>
<span class="stat-label">Recent Posts</span>
</div>
</div>
`;
// Render posts
document.getElementById('postsCard').innerHTML = `
<h3>Recent Posts</h3>
<ul class="post-list">
${posts.map(post => `
<li><strong>${post.title}</strong></li>
`).join('')}
</ul>
`;
// Render todos
document.getElementById('todosCard').innerHTML = `
<h3>Tasks</h3>
<ul class="todo-list">
${todos.map(todo => `
<li class="${todo.completed ? 'completed' : ''}">
${todo.title}
</li>
`).join('')}
</ul>
`;
} catch (error) {
console.error('Dashboard load failed:', error);
showErrorState();
}
}
Example 6: Advanced Error Handling with Retry
Error handling พร้อม retry mechanism และ exponential backoff
JavaScript Code:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
logMessage(`Attempt ${i + 1}/${maxRetries}...`);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
logMessage('✓ Success!', 'success');
return await response.json();
} catch (error) {
lastError = error;
logMessage(`✗ Failed: ${error.message}`, 'error');
if (i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000;
logMessage(`Waiting ${delay}ms before retry...`, 'info');
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
async function testRetry() {
const logContainer = document.getElementById('retryLog');
logContainer.innerHTML = '<h4>Retry Log:</h4>';
try {
const data = await fetchWithRetry('https://httpstat.us/500', {}, 3);
} catch (error) {
logMessage(`Final error: ${error.message}`, 'error');
}
}
function logMessage(message, type = 'info') {
const logContainer = document.getElementById('retryLog');
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logContainer.appendChild(entry);
}
HTTP Client Best Practices
แนวทางที่ดีในการใช้งาน HTTP Client
1. Always Handle Errors
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
} catch (error) {
console.error('Request failed:', error);
// Show user-friendly error message
}
2. Use Loading States
// Show loading before request
setLoading(true);
try {
const data = await fetchData();
renderData(data);
} finally {
// Always hide loading, even if error occurs
setLoading(false);
}
3. Set Timeouts
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request timeout');
}
}
4. Cache Responses
const cache = new Map();
async function fetchWithCache(url, ttl = 60000) {
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < ttl) { return cached.data; } const data=await fetch(url).then(r=> r.json());
cache.set(url, { data, timestamp: Date.now() });
return data;
}