# Cancelando promesas en React

Si estás trabajando con React en algún momento puede haberte llegado a la consola del navegador un aviso sobre una pérdida de memoria al haber actualizado el estado de un componente estando desmontado:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660031517339/7oXJQ3Jrh.png align="left")

Muchos de nosotros evitamos este problema recurriendo a la típica comprobación de `isMounted`, donde básicamente controlábamos el desmonte de un componente de React mediante una referencia que consultábamos justo antes de hacer la actualización del estado. 

> Recordemos que en las primeras versiones de React teníamos esta propiedad de forma interna en la librería, pero [fue deprecada allá por el 2015 porque se trataba de un anti-patrón](https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html)... una mala práctica que el equipo de React avisaba y desaconsejaba su uso. Eso sí, al menos nos mostraban el camino correcto 😅

**Ahora bien, ¿qué podemos hacer para solucionar este problema?**

Podemos continuar con el uso del `isMounted`, pero como bien nos recomienda el artículo, lo suyo sería cancelar la promesa para que no se llegue a producir esa actualización del estado.

Pero si bien la forma anterior está correcta, no es lo más óptimo. Si nos fijamos en lo que sucede en nuestro navegador, vemos cómo efectivamente no tenemos esa pérdida de memoria porque no se intenta actualizar el estado cuando el componente está desmontado, pero lo que sí podemos ver es que nuestra llamada a la API continua su curso, descargando la información de la llamada aunque no haga nada con su respuesta.

> Hemos solucionado un problema pero seguimos arrastrando una pérdida de recursos.

## AbortController con Fetch

Hoy te propongo hacer uso de la interface [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) que te permitirá abortar o cancelar una solicitud web cuando quieras.

Su uso es muy sencillo como verás a continuación:

**Creamos el controlador**

```
const controller = new AbortController();
```

Este objeto nos proporciona un método `abort` que será el que nos permitirá abortar nuestra solicitud web cuando deseemos. También nos retorna una propiedad `signal` que será la que le pasaremos a nuestra configuración `fetch` para que enlace dicha funcionalidad.

Cuando `abort()` sea llamado:
- `controller.signal` emitirá un evento `abort`.
- `controller.signal.aborted` será `true`.

**Cómo lo usamos**

```
const controller = new AbortController();
fetch(url, {
  signal: controller.signal
});
```

Para nuestra suerte, `fetch` permite el uso de la interface `AbortController` así que con el código anterior estará preparado para actuar en caso de que nuestra señal sea abortada.

**Cancelando una llamada**

Ahora para cancelar nuestra llamada a la API, sólo debemos ejecutar el método `abort` de nuestro objeto `controller`:

```
controller.abort();
```

Ahora veremos cómo podemos crearnos un hook para encapsular esta funcionalidad 👇

## useAbortableSignal Hook

```
export const useAbortableSignal = () => {
  const [controller] = useState(() => new AbortController());

  useEffect(() => {
    return () => {
      controller.abort();
    };
  }, []);

  return {
    signal: controller.signal
  };
};


```

### Su uso en un componente

```
export const useData = () => {
  const { signal } = useAbortableSignal();

  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState('');
  const [data, setData] = React.useState([]);

  React.useEffect(() => {
    const fetchData = async () => {
      try {
        const result = await getData(signal);
        setData(result);
      } catch (err) {
        if (err) {
          const isAborted = err.name === 'AbortError';
          if (isAborted) {
            return;
          }

          setError('Something went wrong while fetching data');
        }
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);

  return { isLoading, data, error };
};
```

> Con el código anterior además estás controlando que el error manejado en el `catch` sea porque la solicitud ha sido cancelada y lo podríamos omitir sin problema.

Y ahora si ves el resultado en la pestaña de red de las herramientas de desarrollador... 😊

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660138231811/0DDzLrK2X.png align="left")

### ¿Puedo usar el mismo controlador para varias promesas?

Sí, y de hecho si lo que quieres es cancelar la ejecución de las llamadas a la API en un componente, tiene sentido que todas las solicitudes compartan el mismo controlador porque cuando se desmonte el componente, abortaremos el controlador cancelando todas las solicitudes vivas en el componente.

### Compatibilidad

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660033836413/fg0pQIBBl.png align="left")


En el momento de haber escrito este artículo, la tabla de compatibilidad era la de la imagen, pero te aconsejo que la revises nuevamente en este [enlace](https://developer.mozilla.org/en-US/docs/Web/API/AbortController#browser_compatibility) si tienes alguna duda.

### Código de ejemplo y comparativa

%[https://codesandbox.io/s/abortable-fetch-ffkyzh]

Ver completo: https://codesandbox.io/s/abortable-fetch-ffkyzh

### Recursos
- https://developer.mozilla.org/en-US/docs/Web/API/AbortController
- https://javascript.info/fetch-abort
- https://developer.chrome.com/blog/abortable-fetch/
- https://github.com/whatwg/fetch/issues/27

