Recientemente tuve que crear un widget en React que obtiene datos de múltiples puntos finales de API. A medida que el usuario hace clic, se obtienen nuevos datos y se ordenan en la interfaz de usuario. Pero causó algunos problemas.
Un problema se hizo evidente rápidamente: si el usuario hacía clic lo suficientemente rápido, ya que las solicitudes de red anteriores se resolvían, la interfaz de usuario se actualizaba con datos incorrectos y desactualizados durante un breve período de tiempo.
Podemos eliminar el rebote de nuestras interacciones de la interfaz de usuario, pero eso fundamentalmente no resuelve nuestro problema. Las recuperaciones de red obsoletas resolverán y actualizarán nuestra interfaz de usuario con datos incorrectos hasta que finalice la solicitud de red final y actualice nuestra interfaz de usuario con el estado final correcto. El problema se vuelve más evidente en conexiones más lentas. Además, nos quedamos con solicitudes de redes inútiles que desperdician los datos del usuario.
Aquí hay un ejemplo que construí para ilustrar el problema. Obtiene ofertas de juegos de Steam a través de la genial API Cheap Shark usando el moderno fetch()
método. Intente actualizar rápidamente el límite de precio y verá cómo la interfaz de usuario parpadea con datos incorrectos hasta que finalmente se establece.
La solución
Resulta que hay una manera de abortar las solicitudes asincrónicas DOM pendientes usando un AbortController
. Puede usarlo para cancelar no solo las solicitudes HTTP, sino también los detectores de eventos.
El AbortController
La interfaz representa un objeto controlador que le permite cancelar una o más solicitudes web cuando lo desee.
—Red de desarrolladores de Mozilla
El AbortController
API es simple: expone un AbortSignal
que insertamos en nuestro fetch()
llamadas, así:
const abortController = new AbortController()
const signal = abortController.signal
fetch(url, { signal })
A partir de aquí, podemos llamar abortController.abort()
para asegurarnos de que se cancela nuestra búsqueda pendiente.
Reescribamos nuestro ejemplo para asegurarnos de cancelar las recuperaciones pendientes y ordenar solo los datos más recientes recibidos de la API en nuestra aplicación:
El código es casi el mismo con algunas distinciones clave:
- Crea una nueva variable en caché,
abortController
, en unuseRef
en el<App />
componente. - Para cada nueva búsqueda, inicializa esa búsqueda con una nueva
AbortController
y obtiene su correspondienteAbortSignal
. - pasa lo obtenido
AbortSignal
alfetch()
llamar. - Se aborta en la próxima búsqueda.
const App = () => {
// Same as before, local variable and state declaration
// ...
// Create a new cached variable abortController in a useRef() hook
const abortController = React.useRef()
React.useEffect(() => {
// If there is a pending fetch request with associated AbortController, abort
if (abortController.current) {
abortController.abort()
}
// Assign a new AbortController for the latest fetch to our useRef variable
abortController.current = new AbortController()
const { signal } = abortController.current
// Same as before
fetch(url, { signal }).then(res => {
// Rest of our fetching logic, same as before
})
}, [
abortController,
sortByString,
upperPrice,
lowerPrice,
])
}
Conclusión
¡Eso es! Ahora tenemos lo mejor de ambos mundos: eliminamos el rebote de nuestras interacciones de interfaz de usuario y cancelamos manualmente las recuperaciones de red pendientes obsoletas. De esta manera, estamos seguros de que nuestra interfaz de usuario se actualiza una vez y solo con los datos más recientes de nuestra API.