Planteamiento y contexto
Una página de búsqueda solicita de forma concurrente sugerencias, datos de perfil y resultados. Una nueva consulta o navegación debería cancelar el trabajo anterior rápidamente; una solicitud de red estancada debería agotar el tiempo de espera, pero regresar del segundo plano no debe contar el tiempo congelado como tiempo de solicitud en línea. Diseña la cancelación con AbortSignal.timeout(), AbortSignal.any() y AbortController.
Esto encaja en entrevistas de frontend, rendimiento web y arquitectura asíncrona. La clave es separar las cancelaciones del usuario, los tiempos de espera, el ciclo de vida de la página y los fallos de red reales en lugar de mostrar cada excepción como “se agotó el tiempo de espera de la solicitud”.
Qué está evaluando el entrevistador
Una respuesta sólida indica que AbortSignal.timeout() devuelve una señal que se cancela automáticamente con un TimeoutError; una cancelación por parte del usuario o del controlador suele ser un AbortError. También explica la semántica de tiempo activo: el temporizador se pausa mientras una página está en bfcache o un Worker está suspendido. Se pueden combinar múltiples señales con AbortSignal.any() conservando un motivo diagnosticable.
Preguntas de aclaración para hacer primero
- ¿Qué solicitudes es seguro cancelar, y las escrituras deben completarse o consultarse más tarde?
- ¿El tiempo de espera comienza en la acción del usuario, en el envío de la solicitud o en la visibilidad de la página?
- ¿La cancelación del usuario, el desmontaje, la navegación y el tiempo de espera necesitan mensajes diferentes?
- ¿Los navegadores objetivo admiten los métodos estáticos, y la solución de compatibilidad debería mantener un temporizador manual?
- ¿Cómo se manejan los reintentos, la deduplicación, el almacenamiento en caché y las condiciones de carrera de resultados?
Un marco de respuesta en 30 segundos
“Para cada solicitud combinaría la cancelación del usuario con AbortSignal.timeout() y clasificaría TimeoutError, AbortError y los errores de red por separado. El tiempo de espera utiliza tiempo activo, por lo que la suspensión o bfcache no deberían reportarse como latencia en línea. Una nueva consulta cancela el controlador anterior, y el envío de resultados también verifica una secuencia de solicitud o señal para que los datos obsoletos no puedan imponerse. Los navegadores más antiguos reciben una solución de compatibilidad de controlador más temporizador, probada para cancelación, tiempo de espera, reanudación y condiciones de carrera”.
Respuesta detallada paso a paso
Paso 1: Separar tres fuentes de cancelación
AbortController.abort() se inicia desde la aplicación; AbortSignal.timeout(ms) cancela cuando el tiempo activo alcanza un límite; AbortSignal.any([...]) se dispara cuando cualquiera de las señales de entrada se cancela. Todas se pueden pasar a fetch, pero la capa de negocio debe conservar el motivo para que salir de una página no se registre como un fallo del servicio.
const controller = new AbortController();
const timeout = AbortSignal.timeout(5000);
const signal = AbortSignal.any([controller.signal, timeout]);
fetch(url, { signal });Paso 2: Clasificar por motivo
Un tiempo de espera utiliza una DOMException TimeoutError; una cancelación de usuario o detención del navegador comúnmente utiliza AbortError. Los problemas de DNS, restablecimiento de conexión y CORS pueden aparecer como otros errores. El código que captura excepciones debe inspeccionar signal.reason o el nombre de la excepción antes de elegir el mensaje, el reintento y la gravedad del registro.
Paso 3: Entender el tiempo activo
timeout() mide el tiempo activo en lugar de un simple reloj de pared. La documentación señala que el temporizador se pausa mientras una página está en bfcache o un Worker está suspendido. Eso se adapta a una ventana de solicitud activa percibida por el usuario, no a un límite de tiempo absoluto del servidor; el servidor aún necesita su propia política de tiempo de espera e idempotencia.
Paso 4: Manejar las condiciones de carrera en la búsqueda
Cuando el usuario escribe repetidamente, aborta el controlador anterior antes de crear una señal para la nueva consulta. Incluso si el trabajo de red anterior ha finalizado, su callback aún puede estar en cola; verifica la secuencia de solicitud, la consulta actual o signal.aborted antes de confirmar el estado. La cancelación por sí sola no proporciona ordenamiento por versión de resultado.
Paso 5: Separar lecturas y escrituras cancelables
Las lecturas de búsqueda, imágenes y sugerencias suelen ser cancelables. Las escrituras de pagos, pedidos o subidas pueden haber surtido efecto ya en el servidor. Cancelar la espera del cliente no revierte un efecto secundario en el servidor. Utiliza claves de idempotencia, consultas de estado o una tarea en segundo plano para las escrituras en lugar de simplemente elegir un tiempo de espera más corto.
Paso 6: Diseñar los ciclos de vida de las señales
Aborta un controlador de componente al desmontar, reutiliza una señal a nivel de página en la navegación y agrega un tiempo de espera de solicitud para la operación individual. Nunca compartas una señal global abortada permanentemente con solicitudes posteriores; crea una señal nueva por operación. Proporciona motivos explícitos a diferentes fuentes cuando el diagnóstico sea importante.
Paso 7: Proporcionar una solución de compatibilidad (fallback)
Si un navegador más antiguo carece de AbortSignal.timeout o any, combina AbortController con setTimeout, limpia el temporizador y normaliza el motivo. La detección de características corresponde al tiempo de ejecución, y la ruta predeterminada aún debe manejar entornos sin los métodos estáticos.
Paso 8: Verificar el ciclo de vida y la limpieza
Prueba entradas rápidas, desmontaje, navegación, suspensión en segundo plano, restauración desde bfcache, tiempo de espera, cancelación del usuario, fallo de red y reintentos repetidos. Confirma que la solicitud se cancele, los temporizadores se limpien, los resultados obsoletos no puedan confirmarse, los mensajes sean precisos y que capturar la excepción no genere promesas no controladas ni advertencias de actualización de estado.
Compensaciones y límites
Las señales combinadas reducen el código de integración de cancelación, pero no hacen que una transacción del servidor sea reversible ni garantizan que una respuesta nunca llegue. La semántica de tiempo activo se adapta a la experiencia del usuario, pero no a un SLA de extremo a extremo. Establece los valores de tiempo de espera según el tipo de solicitud, las condiciones de red y el presupuesto de reintentos, coordinándolos con el límite de tiempo del servidor.
No utilices únicamente Promise.race para simular un tiempo de espera dejando la solicitud subyacente en ejecución. Pasa un AbortSignal para que la red y los recursos puedan detenerse. No registres cada cancelación como un error, o la navegación normal contaminará las alertas.
Plan de despliegue y evidencia
Comienza con una máquina de estados de solicitud para lecturas de búsqueda: crea la señal, despacha, clasifica el motivo, confirma un resultado versionado y cancela al desmontar. Registra el tipo de solicitud, motivo, duración activa, recuento de reintentos y estado final sin texto de consulta sensible.
Compara navegadores compatibles y con fallback en bfcache, Workers, redes lentas y entradas rápidas. La aceptación requiere que no haya sobrescritura de resultados obsoletos, ni fugas de temporizadores, mensajes de cancelación precisos, ninguna cancelación accidental de escritura y evidencia de idempotencia o consulta de estado en el servidor.
Errores comunes y preguntas de seguimiento
Confundir TimeoutError y AbortError
La cancelación del usuario, el desmontaje y el tiempo de espera tienen diferentes pasos a seguir. Clasifica por motivo para elegir la mensajería, el registro y el reintento.
Asumir que la cancelación del cliente revierte una escritura en el servidor
Es posible que la solicitud ya haya llegado al servidor. Las escrituras necesitan una clave de idempotencia, consulta de estado o flujo de compensación.
Reemplazar la cancelación real con Promise.race
Hacer una carrera cambia lo que el invocador espera, pero no detiene el fetch subyacente. Pasa AbortSignal y libera los recursos.
Ignorar el tiempo activo en relación con bfcache
El tiempo de espera puede pausarse mientras la página está suspendida. No trates la duración posterior a la restauración como un SLA de solicitud en línea.
¿Cómo evitas que un resultado antiguo prevalezca?
Cancelar el controlador anterior no es suficiente. Compara una secuencia de consulta, versión o consulta actual antes de confirmar para que el orden de respuesta no pueda modificar el estado.