Zac Sokach•
前端错误处理完全指南:从入门到精通
0 分钟阅读0 字
系统地介绍前端错误处理的方方面面,从错误类型识别到完整的错误监控体系,包含大量实战代码示例和最佳实践。
系统地介绍前端错误处理的方方面面,从错误类型识别到完整的错误监控体系,包含大量实战代码示例和最佳实践。
相关文章推荐功能即将上线
昨天凌晨三点,我被电话叫醒:"线上出问题了,用户反馈页面白屏!"赶到公司后,我发现是一个简单的 undefined 错误导致的。这让我意识到,前端错误处理真的是每个开发者都必须掌握的技能。
今天我想系统地聊聊前端错误处理,分享一些实战经验和最佳实践。
首先,我们要了解 JavaScript 中有哪些常见的错误类型:
// 1. ReferenceError:引用错误 console.log(undefinedVariable); // ReferenceError: undefinedVariable is not defined // 2. TypeError:类型错误 const obj = null; obj.method(); // TypeError: Cannot read property 'method' of null // 3. SyntaxError:语法错误 const fn = function () {; // SyntaxError: Unexpected token ';' // 4. RangeError:范围错误 new Array(-1); // RangeError: Invalid array length // 5. 自定义错误 class CustomError extends Error { constructor(message) { super(message); this.name = 'CustomError'; } }
异步操作中的错误往往更难发现:
// Promise 中的错误 fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data.items.length); // 如果 data 没有 items 属性,会报错 }) .catch(error => { console.error('Fetch error:', error); }); // async/await 中的错误 async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data.items.map(item => item.name); // 可能出错 } catch (error) { console.error('Error:', error); return []; // 返回默认值 } }
每个应用都应该有全局错误处理机制:
// 捕获同步错误 window.addEventListener('error', (event) => { console.error('Global error:', event.error); // 发送错误到监控系统 sendErrorToMonitoring({ message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack }); }); // 捕获 Promise 错误 window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); // 防止错误在控制台显示 event.preventDefault(); // 发送错误到监控系统 sendErrorToMonitoring({ type: 'promise', reason: event.reason, stack: event.reason?.stack }); });
在函数中,我遵循这些原则:
// 1. 参数验证 function processUser(user) { if (!user || typeof user !== 'object') { throw new Error('Invalid user object'); } if (!user.id || !user.name) { throw new Error('User must have id and name'); } // 处理逻辑... } // 2. 防御性编程 function getUserDisplayName(user) { // 使用可选链和空值合并 return user?.profile?.displayName ?? user?.name ?? '匿名用户'; } // 3. 默认值和回退方案 function getConfig(key, defaultValue = null) { try { return config[key] ?? defaultValue; } catch (error) { console.warn(`Failed to get config for key: ${key}`, error); return defaultValue; } }
当某些功能出错时,提供替代方案:
// 图片加载失败的处理 function SafeImage({ src, alt, fallback }) { const [imgSrc, setImgSrc] = useState(src); const [hasError, setHasError] = useState(false); const handleError = () => { if (!hasError && fallback) { setImgSrc(fallback); setHasError(true); } }; return ( <img src={imgSrc} alt={alt} onError={handleError} style={{ opacity: hasError ? 0.7 : 1 }} /> ); } // API 请求的重试机制 async function fetchWithRetry(url, options = {}, retries = 3) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); } catch (error) { if (retries > 0) { console.warn(`Retrying... attempts left: ${retries}`); await new Promise(resolve => setTimeout(resolve, 1000)); return fetchWithRetry(url, options, retries - 1); } throw error; } }
简单的错误监控系统:
class ErrorMonitor { constructor(config) { this.config = { apiUrl: '/api/errors', maxErrors: 50, ...config }; this.errorQueue = []; this.isSending = false; } // 收集错误信息 collectError(errorInfo) { const enrichedError = { ...errorInfo, timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href, userId: this.getCurrentUserId(), sessionId: this.getSessionId() }; this.errorQueue.push(enrichedError); // 限制队列大小 if (this.errorQueue.length > this.config.maxErrors) { this.errorQueue.shift(); } this.sendErrors(); } // 发送错误到服务器 async sendErrors() { if (this.isSending || this.errorQueue.length === 0) { return; } this.isSending = true; const errorsToSend = [...this.errorQueue]; this.errorQueue = []; try { await fetch(this.config.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ errors: errorsToSend }) }); } catch (error) { console.error('Failed to send errors:', error); // 失败的 errors 重新加入队列 this.errorQueue.unshift(...errorsToSend); } finally { this.isSending = false; } } }
// 统一的 API 请求处理 class ApiClient { constructor(baseURL) { this.baseURL = baseURL; } async request(endpoint, options = {}) { const url = `${this.baseURL}${endpoint}`; try { const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { throw new ApiError(response.status, response.statusText); } return await response.json(); } catch (error) { if (error instanceof ApiError) { throw error; } throw new NetworkError('Network request failed', error); } } } class ApiError extends Error { constructor(status, message) { super(message); this.name = 'ApiError'; this.status = status; } } class NetworkError extends Error { constructor(message, originalError) { super(message); this.name = 'NetworkError'; this.originalError = originalError; } }
// 安全的数据渲染 function SafeDataDisplay({ data }) { if (!data) { return <div>暂无数据</div>; } return ( <div> <h1>{data.title || '无标题'}</h1> <p>{data.description || '暂无描述'}</p> <div> {(data.items || []).map(item => ( <div key={item.id}> {item.name || '未命名项目'} </div> ))} </div> </div> ); }
// 表单验证和错误处理 function useFormValidation(initialValues, validationSchema) { const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const validate = (fieldValues = values) => { const newErrors = {}; Object.keys(validationSchema).forEach(field => { const rules = validationSchema[field]; const value = fieldValues[field]; if (rules.required && (!value || value.trim() === '')) { newErrors[field] = `${field} 是必填项`; } if (rules.minLength && value.length < rules.minLength) { newErrors[field] = `${field} 至少需要 ${rules.minLength} 个字符`; } if (rules.pattern && !rules.pattern.test(value)) { newErrors[field] = `${field} 格式不正确`; } }); return newErrors; }; const handleSubmit = async (onSubmit) => { const validationErrors = validate(); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); return; } setIsSubmitting(true); setErrors({}); try { await onSubmit(values); } catch (error) { setErrors({ submit: error.message }); } finally { setIsSubmitting(false); } }; return { values, errors, isSubmitting, setValues, handleSubmit }; }
// 不要这样 catch (error) { alert('Error: ' + error.message); } // 应该这样 catch (error) { console.error('Detailed error:', error); // 根据错误类型显示不同的用户友好信息 let userMessage = '操作失败,请稍后重试'; if (error instanceof NetworkError) { userMessage = '网络连接失败,请检查网络设置'; } else if (error instanceof ValidationError) { userMessage = '输入信息有误,请检查后重试'; } showUserMessage(userMessage, 'error'); }
// 不要这样 try { riskyOperation(); } catch (error) { // 什么都不做 } // 应该这样 try { riskyOperation(); } catch (error) { console.error('Operation failed:', error); // 至少记录错误,或者提供回退方案 provideFallback(); }
// 自动重试 function withRetry(fn, maxRetries = 3, delay = 1000) { return async (...args) => { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await fn(...args); } catch (error) { lastError = error; if (i < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i))); } } } throw lastError; }; } // 使用示例 const fetchWithRetry = withRetry(fetchData);
错误处理是前端开发中不可或缺的一部分。一个好的错误处理机制应该:
记住,错误处理不是一次性的工作,而是需要持续优化和完善的过程。希望这篇文章能帮助你更好地处理前端错误,构建更稳定的应用!
最后,如果你有什么错误处理的经验或问题,欢迎在评论区交流!