Saltar al contenido

Patrones robustos: límites, logging y reintento

Industrializar la resiliencia con ErrorBoundary: limites de error por seccion para aislar fallos, logging centralizado con onError, y recuperacion con reset combinado con refetch de recursos, backoff exponencial y freno a los bucles de reintento.

Límites por sección

Un único ErrorBoundary en la raíz convierte cualquier error en una pantalla en blanco. La estrategia robusta es la contraria: muchos límites pequeños, uno por región independiente —la barra lateral, el feed, cada tarjeta— de modo que un fallo se contenga y el resto siga vivo. Un helper Seccion lo hace repetible:

TSX
import { ErrorBoundary } from "solid-js";function Seccion(props: { nombre: string; children: JSX.Element }) {  return (    <ErrorBoundary      fallback={(err, reset) => (        <div role="alert" class="seccion-caida">          <p>La sección {props.nombre} no se pudo cargar.</p>          <button onClick={reset}>Reintentar</button>        </div>      )}    >      {props.children}    </ErrorBoundary>  );}// si Feed falla, Barra y Perfil siguen en pie<>  <Seccion nombre="barra"><Barra /></Seccion>  <Seccion nombre="feed"><Feed /></Seccion>  <Seccion nombre="perfil"><Perfil /></Seccion></>;

La granularidad es una decisión de diseño: demasiado gruesa y un fallo tira de más; demasiado fina y llenas la interfaz de mensajes de error. La regla útil es un límite por unidad de valor independiente para el usuario.

Logging: onError junto al fallback

El fallback presenta el error al usuario; el registro para el equipo es otra responsabilidad, y conviene separarla. onError capta el error durante su propagación y lo envía a tu telemetría sin decidir nada de la UI:

TSX
import { onError } from "solid-js";function ConRegistro(props: { area: string; children: JSX.Element }) {  onError((err) => {    reportar({ area: props.area, mensaje: String(err), cuando: Date.now() });  });  return <>{props.children}</>;}// se compone con el limite: registra y ademas muestra fallback<Seccion nombre="feed">  <ConRegistro area="feed"><Feed /></ConRegistro></Seccion>;

Como onError no detiene la propagación, el error sigue subiendo hasta el ErrorBoundary de la Seccion, que pinta el fallback. Registro y presentación quedan desacoplados y componibles: puedes envolver con ConRegistro cualquier parte, con o sin límite propio.

Recuperación: reset, refetch y backoff

reset vuelve a montar los hijos, pero si la causa persiste el error reaparece al instante. Para una recuperación real necesitas cambiar algo entre el fallo y el reintento. Con datos async, ese algo es refetch: disparas una nueva carga del recurso y después llamas a reset para que el render vuelva a intentarse sobre datos frescos.

TSX
const [datos, { refetch }] = createResource(fuente, cargar);<ErrorBoundary  fallback={(err, reset) => (    <Fallo      mensaje={err.message}      onReintentar={() => {        refetch(); // nueva peticion        reset();   // y re-render del subarbol sobre el recurso recargado      }}    />  )}>  <Vista datos={datos()} /></ErrorBoundary>;

Falta el freno. Un reintento que siempre falla y siempre se reintenta es un bucle. La defensa es un tope de intentos con backoff exponencial: cuenta los reintentos en un signal del owner —sobrevive a los reset—, espacia cada reintento y ríndete al superar el máximo.

TSX
import { ErrorBoundary, Show, createSignal } from "solid-js";function ConReintentos(props: { max?: number; children: JSX.Element }) {  const max = props.max ?? 3;  const [intentos, setIntentos] = createSignal(0);  return (    <ErrorBoundary      fallback={(err, reset) => (        <div role="alert">          <p>{err.message}</p>          <Show            when={intentos() < max}            fallback={<p>Se agotaron los reintentos. Contacta con soporte.</p>}          >            <button              onClick={() => {                setIntentos((n) => n + 1);                setTimeout(reset, 2 ** intentos() * 200); // 200, 400, 800 ms              }}            >              Reintentar {intentos()} de {max}            </button>          </Show>        </div>      )}    >      {props.children}    </ErrorBoundary>  );}