您现在的位置是:网站首页 > 不处理任何错误('fetch().then(data => use(data))')文章详情
不处理任何错误('fetch().then(data => use(data))')
陈川
【
前端综合
】
30451人已围观
4680字
直接忽略错误的后果
fetch().then(data => use(data))
这种写法在前端代码中随处可见。开发者只关心成功回调,完全无视可能发生的网络错误、服务器错误或数据解析错误。当API返回500状态码时,控制台不会显示任何异常,用户界面可能显示空白或卡在加载状态。更糟糕的是,当JSON解析失败时,整个应用可能直接崩溃。
// 典型的问题代码
fetch('/api/data')
.then(response => response.json())
.then(data => renderDashboard(data))
错误传播的沉默中断
Promise链中的错误如果没有被捕获,会像气泡一样向上传递直到被捕获。但在这个模式中,错误永远找不到处理程序。现代浏览器会在控制台输出"Uncaught (in promise)"警告,但在生产环境中用户看不到这些信息。React 16之后,未被捕获的Promise错误甚至会导致整个组件树卸载。
// 实际发生的错误处理流程
fetch('/api') // 1. 网络错误
.then(res => res.json()) // 2. 跳过(因为上一个Promise被reject)
.then(data => console.log(data)) // 3. 永远不会执行
真实场景的灾难案例
电商网站的商品列表页使用这种模式加载数据。当CDN出现故障时:
- 用户看到无限加载动画
- 埋点系统收不到任何错误日志
- 运维无法收到监控报警
- 客服接到大量投诉却无法定位问题
// 实际业务中的危险代码
function loadProducts() {
fetch('/api/products')
.then(res => res.json())
.then(products => {
document.getElementById('list').innerHTML =
products.map(p => `<div>${p.name}</div>`).join('')
})
}
错误处理的基础方案
最简单的修复是添加catch
子句,至少将错误输出到控制台。对于关键业务操作,应该实现错误重试机制。axios等HTTP库会自动抛出非2xx状态码的错误,但fetch API需要手动处理。
// 基础错误处理
fetch('/api')
.then(res => {
if (!res.ok) throw new Error(res.statusText)
return res.json()
})
.then(data => use(data))
.catch(err => {
console.error('请求失败:', err)
showErrorToast(err.message)
})
高级错误处理模式
对于生产环境应用,应该实现分层的错误处理策略:
- 网络层:处理CORS、超时、离线状态
- HTTP层:处理403、404、500等状态码
- 业务层:处理数据格式错误、空数据
- UI层:显示友好的错误提示
// 分层错误处理示例
async function loadUserProfile(userId) {
try {
const response = await fetch(`/api/users/${userId}`, {
signal: AbortSignal.timeout(5000)
});
if (response.status === 404) {
return navigate('/not-found')
}
const data = await response.json();
if (!data.profile) {
throw new Error('Invalid profile data')
}
return renderProfile(data);
} catch (error) {
if (error.name === 'AbortError') {
showToast('请求超时,请重试')
} else {
logErrorToService(error)
showErrorPage(error)
}
}
}
框架中的最佳实践
现代前端框架提供了更好的错误处理机制。React Error Boundary可以捕获组件树中的错误,Vue的errorCaptured钩子也有类似功能。结合这些机制可以构建更健壮的应用。
// React错误边界示例
class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack)
}
render() {
return this.state.hasError
? <FallbackUI />
: this.props.children
}
}
// 使用方式
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
监控与日志记录
仅仅捕获错误还不够,还需要建立完整的监控体系。Sentry、Bugsnag等工具可以捕获客户端错误并关联用户会话信息。对于关键路径操作,应该记录完整的错误上下文。
// 使用Sentry记录错误
import * as Sentry from '@sentry/browser'
fetch('/api')
.then(processResponse)
.catch(error => {
Sentry.captureException(error, {
contexts: {
request: {
url: '/api',
method: 'GET'
}
}
})
})
用户体验的考量
错误处理不仅是技术问题,更是用户体验问题。不同类型的错误需要不同的处理方式:
- 网络错误:提供重试按钮
- 权限错误:引导到登录页
- 服务器错误:显示维护页面
- 数据错误:展示降级UI
// 根据错误类型显示不同UI
function handleError(error) {
if (error instanceof NetworkError) {
return <NetworkErrorModal onRetry={fetchData} />
}
if (error instanceof AuthError) {
return <Redirect to="/login" />
}
return <GenericError message={error.message} />
}
测试策略的调整
完整的错误处理需要相应的测试覆盖。除了测试正常流程,还应该专门测试各种错误场景。Jest等测试框架支持测试异步错误。
// 测试错误处理的示例
test('handles 404 errors', async () => {
fetch.mockResponseOnce('', { status: 404 })
const { getByText } = render(<UserPage />)
await waitFor(() => {
expect(getByText('User not found')).toBeInTheDocument()
})
})
TypeScript的增强保护
TypeScript可以在编译时帮助发现一些潜在的错误处理问题。通过配置strictNullChecks
和自定义类型保护,可以强制处理可能的空值或错误状态。
interface ApiResponse<T> {
data?: T
error?: {
code: number
message: string
}
}
async function fetchData<T>(url: string): Promise<T> {
const response: ApiResponse<T> = await fetch(url).then(r => r.json())
if (response.error) {
throw new Error(response.error.message)
}
if (!response.data) {
throw new Error('Empty response')
}
return response.data
}
性能与错误处理的平衡
过度防御的错误处理可能影响性能。关键是要在关键路径上进行必要的检查,同时避免过多的try-catch块影响V8的优化。对于非关键操作,可以考虑延迟错误处理。
// 性能优化的错误处理
function parseJSONSafely(json) {
// 使用函数隔离try-catch避免影响外层性能
try {
return JSON.parse(json)
} catch {
return null
}
}
async function loadConfig() {
const response = await fetch('/config.json')
const text = await response.text()
return parseJSONSafely(text) || loadDefaultConfig()
}