Skip to content

Error Handling

O Slash fornece ferramentas para capturar e tratar erros tanto síncronos quanto assíncronos, em client-side e server-side rendering (SSR).

  • ErrorBoundary: Componente com fallback (com uma limitação importante, veja abaixo)
  • catchAsync: Wrapper para funções assíncronas com tratamento de erro
  • safeRender: Executa uma view e devolve um fallback se ela lançar (cliente e SSR). É a forma de proteger uma subárvore
  • setupGlobalErrorHandler: Handler global para erros não capturados

O ErrorBoundary recebe fallback, onError e children.

type ErrorBoundaryProps = {
/** Obrigatório: UI de fallback quando um erro é capturado */
fallback: (error: Error) => Child
/** Callback opcional chamado quando um erro é capturado */
onError?: (error: Error, errorInfo: { componentStack?: string }) => void
/** Children a serem renderizados */
children: Child
}
import { html, safeRender } from '@_bashell/slash'
// Componentes de exemplo
function UserWidget() {
return html`<section>Usuário</section>`
}
function StatsWidget(): never {
throw new Error('Falha ao montar as estatísticas')
}
const widgetError = (message: string) => (error: Error) => html`
<div class="widget-error">
<p>${message}</p>
<small>${error.message}</small>
</div>
`
function Dashboard() {
return html`
<div class="dashboard">
<h1>Dashboard</h1>
${safeRender(() => html`<${UserWidget} />`, widgetError('Usuário indisponível.'))}
${safeRender(() => html`<${StatsWidget} />`, widgetError('Erro ao carregar estatísticas.'))}
</div>
`
}

Cada safeRender é independente: se um widget falhar, os outros continuam funcionando. safeRender registra o erro com console.error e devolve o resultado de fallback.

catchAsync é um wrapper para funções assíncronas que captura erros automaticamente:

function catchAsync<T>(
fn: () => Promise<T>,
onError?: (error: Error) => void
): [
safeFn: () => Promise<T | null>,
getError: () => Error | null
]
import { html, createState, render, catchAsync } from '@_bashell/slash'
// State e função segura ficam fora do componente: o componente re-executa
// quando o que ele lê muda, e criar/disparar isso no corpo repetiria o fetch
const userData = createState<any>(null)
let userId = 1
// Criar função segura que captura erros
const [fetchUser, getError] = catchAsync(
async () => {
const res = await fetch(`/api/users/${userId}`)
if (!res.ok) throw new Error('Falha ao carregar usuário')
return res.json()
},
(error) => {
console.error('Erro ao buscar usuário:', error)
}
)
const loadUser = async () => {
const data = await fetchUser()
if (data) userData.set(data)
}
function UserProfile() {
// getError() e userData.get() inscrevem o componente
const error = getError()
const user = userData.get()
if (error) {
return html`
<div class="error">
<p>Erro: ${error.message}</p>
<button onClick=${loadUser}>Tentar novamente</button>
</div>
`
}
if (!user) {
return html`<p>Carregando...</p>`
}
return html`
<div class="user-profile">
<h2>${user.name}</h2>
<p>${user.email}</p>
</div>
`
}
// Monte como <${UserProfile} /> e dispare o carregamento fora do componente
render(html`<${UserProfile} />`, '#app')
loadUser()

Exemplo: Múltiplas operações assíncronas

Section titled “Exemplo: Múltiplas operações assíncronas”
import { html, createState, render, catchAsync } from '@_bashell/slash'
const data = createState<any>(null)
const [fetchData1, getError1] = catchAsync(async () => {
const res = await fetch('/api/data1')
return res.json()
})
const [fetchData2, getError2] = catchAsync(async () => {
const res = await fetch('/api/data2')
return res.json()
})
const [fetchData3, getError3] = catchAsync(async () => {
const res = await fetch('/api/data3')
return res.json()
})
// Executar todas as requisições (fora do componente)
const loadAll = () =>
Promise.all([fetchData1(), fetchData2(), fetchData3()]).then(
([data1, data2, data3]) => {
data.set({ data1, data2, data3 })
}
)
function DataDashboard() {
// Estas leituras inscrevem o componente
const error1 = getError1()
const error2 = getError2()
const error3 = getError3()
const current = data.get()
return html`
<div>
${error1 && html`<p class="error">Erro ao carregar dados 1</p>`}
${error2 && html`<p class="error">Erro ao carregar dados 2</p>`}
${error3 && html`<p class="error">Erro ao carregar dados 3</p>`}
${current && html`<pre>${JSON.stringify(current, null, 2)}</pre>`}
</div>
`
}
render(html`<${DataDashboard} />`, '#app')
loadAll()

safeRender é um wrapper para renderização segura, especialmente útil para SSR:

function safeRender(
view: () => Child,
fallback: (error: Error) => Child
): Child
import { renderToString, serializeStateForScript } from '@_bashell/slash/ssr'
import { safeRender } from '@_bashell/slash'
// No servidor (Express). `App` é um componente feito com htmlString
app.get('*', (req, res) => {
const body = safeRender(
() => {
const { html, state } = renderToString(() => App({ url: req.url }))
return `<div id="root">${html}</div>
<script id="__SLASH_STATE__" type="application/json">${serializeStateForScript(state)}</script>`
},
(error) => {
console.error('Erro SSR:', error)
return `<div class="error">Erro ao renderizar página</div>`
}
)
// O documento é um template literal comum (não use htmlString com <!DOCTYPE>)
res.send(`<!DOCTYPE html>
<html>
<body>
${body}
<script src="/client.js"></script>
</body>
</html>`)
})
import { html } from '@_bashell/slash'
import { safeRender } from '@_bashell/slash'
function DangerousComponent() {
return safeRender(
() => {
// Código que pode lançar erro
const data = JSON.parse(someUntrustedString)
return html`<div>${data.value}</div>`
},
(error) => html`
<div class="error">
<p>Erro ao processar dados</p>
<pre>${error.message}</pre>
</div>
`
)
}

Configura um handler global para capturar todos os erros não tratados:

function setupGlobalErrorHandler(
onError: (error: Error, source: 'render' | 'runtime') => void
): void
import { setupGlobalErrorHandler } from '@_bashell/slash'
// Configurar no início da aplicação (client-side)
setupGlobalErrorHandler((error, source) => {
console.error(`Erro ${source}:`, error)
// Enviar para serviço de monitoramento
if (typeof Sentry !== 'undefined') {
Sentry.captureException(error, {
tags: { source }
})
}
// Exibir toast/notificação para o usuário
showErrorNotification('Algo deu errado. Por favor, recarregue a página.')
})

Exemplo: Sistema completo de error handling

Section titled “Exemplo: Sistema completo de error handling”
import { html, render, safeRender, setupGlobalErrorHandler } from '@_bashell/slash'
// 1. Handler global para o que escapa de tudo (handlers, promises, erros de runtime)
setupGlobalErrorHandler((error, source) => {
console.error(`[Global] Erro ${source}:`, error)
// envie para o seu serviço de logging
})
function App() {
return html`<main><h1>Minha aplicação</h1></main>`
}
// 2. Proteja a montagem da raiz com safeRender
const root = safeRender(
() => html`<${App} />`,
(error) => html`
<div class="app-error">
<h1>Erro Fatal</h1>
<p>A aplicação encontrou um erro inesperado.</p>
<button onClick=${() => window.location.reload()}>Recarregar página</button>
<details>
<summary>Detalhes</summary>
<pre>${error.stack}</pre>
</details>
</div>
`
)
render(root, '#root')
// Ruim: uma única proteção para tudo (um erro derruba a página inteira)
const page = safeRender(
() => html`<div><${Header} /><${Sidebar} /><${MainContent} /><${Footer} /></div>`,
pageFallback
)
// Bom: cada seção crítica isolada
const page = html`
<div>
<${Header} />
${safeRender(() => html`<${Sidebar} />`, sidebarFallback)}
${safeRender(() => html`<${MainContent} />`, contentFallback)}
<${Footer} />
</div>
`
// ❌ Ruim: Mensagem genérica
const fallback = (error: Error) => html`<div>Erro</div>`
// ✅ Bom: Informação útil + ação
const betterFallback = (error: Error) => html`
<div class="error-widget">
<p>Não foi possível carregar os dados.</p>
<button onClick=${retry}>Tentar novamente</button>
<a href="/help">Precisa de ajuda?</a>
</div>
`
import { html, safeRender } from '@_bashell/slash'
const view = safeRender(
() => html`<${Content} />`,
(error) => {
// Analytics / Sentry / log interno
trackError(error)
if (typeof Sentry !== 'undefined') {
Sentry.captureException(error)
}
return html`<p>Algo deu errado.</p>`
}
)

4. Trate erros assíncronos explicitamente

Section titled “4. Trate erros assíncronos explicitamente”
// ❌ Ruim: Erro silencioso
async function loadData() {
const res = await fetch('/api/data')
const data = await res.json()
return data
}
// ✅ Bom: Tratamento explícito
async function loadData() {
try {
const res = await fetch('/api/data')
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
return await res.json()
} catch (error) {
console.error('Erro ao carregar dados:', error)
throw error // Re-throw: quem chama decide (ex.: catchAsync ou try/catch)
}
}

Veja o exemplo completo em Uso em SSR: o documento é montado com template literal comum, e o fallback devolve uma string HTML montada à mão, o que só funciona porque ela entra num template literal comum. Dentro de html/htmlString, uma string é sempre texto: devolva um template (ou unsafeHtml(...) para marcação confiável).

FrameworkError BoundaryAsync ErrorsGlobal Handler
Slash⚠️ ErrorBoundary (limitado) + safeRender✅ catchAsync✅ setupGlobalErrorHandler
React✅ Class-based❌ Manual try/catch✅ window.onerror
Vue✅ errorCaptured❌ Manual✅ app.config.errorHandler
Solid✅ ErrorBoundary❌ Manual✅ onError
  • SSR - Error handling no servidor
  • Componentes - Criando componentes resilientes
  • Estado - Gerenciamento de estado com tratamento de erro