錯誤處理是程式設計的一個基本方面,確保應用程式能夠優雅地處理意外情況。在 JavaScript 中,雖然 try-catch
是常用的,但還有更先進的技術來增強錯誤處理。
本文探討這些先進的方法,提供實用的解決方案來改善您的錯誤管理策略,使您的應用程式更加穩健。
什麼是錯誤處理?
錯誤處理的目的
錯誤處理預測、檢測並響應在程式執行過程中出現的問題。適當的錯誤處理改善了用戶體驗,維持應用程式的穩定性,並確保可靠性。
JavaScript 中的錯誤類型
- 語法錯誤。這些是程式碼語法中的錯誤,例如缺少括號或關鍵字的錯誤使用。
- 運行時錯誤。在執行過程中發生,例如訪問未定義對象的屬性。
- 邏輯錯誤。這些錯誤不會導致程式崩潰,但會導致不正確的結果,通常是由於邏輯錯誤或意外的副作用。
為什麼 try-catch 不夠用
try-catch 的限制
- 範圍限制。僅處理其區塊內的同步代碼,並且不會影響異步操作,除非特別處理。
- 靜默失敗。過度使用或不當使用可能導致錯誤被靜默忽略,潛在地造成意外行為。
- 錯誤傳播。不原生支持在應用程序的不同層級之間傳播錯誤。
何時使用 try-catch
- 同步代碼。對於處理 JSON 解析等同步操作中的錯誤非常有效。
- 關鍵區域。用於保護關鍵代碼區域,因為錯誤可能會有嚴重後果。
自定義錯誤類:增強錯誤信息
創建自定義錯誤類
自定義錯誤類擴展內建的 Error
類,以提供額外的信息:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.stack = (new Error()).stack; // Capture the stack trace
}
}
自定義錯誤的好處
- 清晰度。提供特定的錯誤消息。
- 細粒度處理。允許單獨處理特定錯誤類型。
- 錯誤元數據。包含有關錯誤的額外上下文。
自定義錯誤的使用案例
- 驗證失敗。與用戶輸入驗證相關的錯誤。
- 領域特定錯誤。針對特定應用領域(如身份驗證或支付處理)量身定制的錯誤。
集中錯誤處理
Node.js 中的全局錯誤處理
使用中介軟件集中錯誤處理:
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({ message: 'An error occurred' });
});
前端應用中的集中錯誤處理
在 React 中使用錯誤邊界實現集中式錯誤處理:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
集中式錯誤處理的優點
- 一致性。確保對錯誤管理採取統一的方法。
- 更易於維護。集中式更新減少遺漏變更的風險。
- 更好的日誌記錄和監控。促進與監控工具的整合。
錯誤傳播
同步代碼中的錯誤傳播
使用 throw
來傳播錯誤:
function processData(data) {
try {
validateData(data);
saveData(data);
} catch (error) {
console.error('Failed to process data:', error);
throw error;
}
}
異步代碼中的錯誤傳播
使用 promises 或 async
/await
處理錯誤:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
何時傳播錯誤
- 關鍵錯誤。傳播影響整個應用程序的錯誤。
- 商業邏輯。允許高級組件處理商業邏輯錯誤。
異步代碼中的錯誤處理
使用 async/await 的錯誤處理
使用 try-catch
管理異步函數中的錯誤:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return await response.json();
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
使用 Promise.all 進行錯誤處理
處理多個 promises 和錯誤:
async function fetchMultipleData(urls) {
try {
const responses = await Promise.all(urls.map
(url => fetch(url)));
return await Promise.all(responses.map(response => {
if (!response.ok) {
throw new Error(`Failed to fetch ${response.url}`);
}
return response.json();
}));
} catch (error) {
console.error('Error fetching multiple data:', error);
return [];
}
}
異步錯誤處理中的常見陷阱
- 未捕獲的 promises。始終使用
await
、.then()
或.catch()
處理 promises。 - 靜默失敗。確保錯誤不會被靜默吞噬。
- 競爭條件。在處理並發的 非同步操作 時要小心。
錯誤日誌
客戶端錯誤日誌
捕獲全局錯誤:
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error captured:', message, source, lineno, colno, error);
sendErrorToService({ message, source, lineno, colno, error });
};
伺服器端錯誤日誌
使用像 Winston 的工具進行伺服器端日誌記錄:
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'error.log' })]
});
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('An error occurred');
});
監控與警報
使用像 PagerDuty 或 Slack 的服務設置實時監控和警報:
function notifyError(error) {
// Send error details to monitoring service
}
錯誤日誌的最佳實踐
- 包含上下文。記錄請求數據和用戶信息等額外上下文。
- 避免過度日誌記錄。記錄必要的信息以防止性能問題。
- 定期分析日誌。定期檢查日誌以檢測並解決重複出現的問題。
優雅降級和備援
優雅降級
設計您的應用程序以在功能減少的情況下繼續運行:
function renderProfilePicture(user) {
try {
if (!user.profilePicture) {
throw new Error('Profile picture not available');
}
return `<img data-fr-src="${user.profilePicture}" alt="Profile Picture">`;
} catch (error) {
console.error('Error rendering profile picture:', error.message);
return '<img src="/default-profile.png" alt="Default Profile Picture">';
}
}
備援機制
當主要操作失敗時提供替代方案:
async function fetchDataWithFallback(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
return { message: 'Default data' }; // Fallback data
}
}
實施優雅降級
- UI 備援。當功能失效時提供替代的 UI 元素。
- 數據備援。當即時數據不可用時使用緩存或默認值。
- 重試機制。對於暫時性錯誤實施重試邏輯。
平衡優雅降級
平衡提供備援與讓用戶了解問題:
function showErrorNotification(message) {
// Notify users about the issue
}
測試錯誤處理
單元測試錯誤處理
驗證單個函數中的錯誤處理:
const { validateUserInput } = require('./validation');
test('throws error for invalid username', () => {
expect(() => {
validateUserInput({ username: 'ab' });
}).toThrow('Username must be at least 3 characters long.');
});
整合測試
測試不同應用層之間的錯誤處理:
test('fetches data with fallback on error', async () => {
fetch.mockReject(new Error('Network error'));
const data = await fetchDataWithFallback('https://api.example.com/data');
expect(data).toEqual({ message: 'Default data' });
});
端到端測試
模擬現實場景以測試錯誤處理:
describe('ErrorBoundary', () => {
it('displays error message on error', () => {
cy.mount(<ErrorBoundary><MyComponent /></ErrorBoundary>);
cy.get(MyComponent).then(component => {
component.simulateError(new Error('Test error'));
});
cy.contains('Something went wrong.').should('be.visible');
});
});
測試錯誤處理的最佳實踐
- 涵蓋邊緣案例。確保測試涵蓋各種錯誤場景。
- 測試備援。驗證備援機制按預期運作。
- 自動化測試。使用CI/CD 管道來自動化並確保穩健的錯誤處理。
現實場景
場景 1:支付處理系統
處理支付過程中的錯誤:
- 自訂錯誤類別。使用類別如
CardValidationError
、PaymentGatewayError
。 - 重試邏輯。對網絡相關問題實施重試。
- 集中日誌記錄。監控支付錯誤並及時解決問題。
場景 2:數據密集型應用
管理數據處理中的錯誤:
- 優雅降級。提供部分數據或替代視圖。
- 備用數據。使用快取或預設值。
- 錯誤記錄。記錄詳細的上下文以進行故障排除。
場景 3:用戶身份驗證和授權
處理身份驗證和授權錯誤:
- 自定義錯誤類別。創建類別,如
AuthenticationError
、AuthorizationError
。 - 集中處理。記錄和監控與身份驗證相關的問題。
- 優雅降級。提供替代登錄選項和有意義的錯誤消息。
結論
在 JavaScript 中進行高級錯誤處理需要超越簡單的 try-catch
,擁抱自定義錯誤、集中處理、傳播和穩健測試。實施這些技術可以讓您構建出即使在出現問題時也能提供無縫用戶體驗的韌性應用程序。
進一步閱讀
- 道格拉斯·克羅克福德 (Douglas Crockford) 的《JavaScript: The Good Parts》
- 凱爾·辛普森 (Kyle Simpson) 的《You Don’t Know JS: Async & Performance》
- MDN Web 文檔:錯誤處理
Source:
https://dzone.com/articles/advanced-error-handling-in-javascript-custom-error