Cancellable promises in react and why is it required
Published: 2020-07-18 07:31:26

### Prerequisite:
- Basic understanding of react component and react hooks.
- Basic understanding on how the promises work.
- Basic knowledge of Typescript.

I will be using react with typescript (tsx) for examples. However, the same code can be used in js files without the type definitions.   

---   

Promises once fired, cannot be cancelled. So cancelling the promises in our current context means to ignore the result of the promise. When an api call is fired inside a react component, any state update after the component is destroyed (inside the then block of the promise), will cause error.

In order to avoid this, lets write a function that takes a normal promise and converts that into a cancellable promise which does not invoke any state updates on the component that is destroyed.   

```typescript
export const cancellablePromise = (promise: Promise<any | void>) => {
    const isCancelled = { value: false };
    const wrappedPromise = new Promise((res, rej) => {
        promise
            .then(d => {
                return isCancelled.value ? rej(isCancelled) : res(d);
            })
            .catch(e => {
                rej(isCancelled.value ? isCancelled : e);
            });
    });

    return {
        promise: wrappedPromise,
        cancel: () => {
            isCancelled.value = true;
        }
    };
};
```   
<br />

Lets break down the above code and try to understand whats happening.   

```typescript
export const cancellablePromise = (promise: Promise<any | void>) => {
```   
<br />

This function takes a single argument, which is the promise that needs to be cancelled when the component that is using the promise is destroyed.   

```typescript
const isCancelled = { value: false };
```   
<br />

We declare an object that holds a value that states whether the current promise is cancelled or not.   
This value will be changed to `true` when the component is destroyed, to indicate that the promise is cancelled.   

```typescript
const wrappedPromise = new Promise((res, rej) => {
    promise
        .then(d => {
            return isCancelled.value ? rej(isCancelled) : res(d);
        })
        .catch(e => {
            rej(isCancelled.value ? isCancelled : e);
        });
});
```   
<br />

Here, we create one more promise, a wrapped one, that takes the original promise and rejects if the promise is cancelled (`value` is `true`). If the promise is not cancelled (`value` is `false`), only then the result is resolved. Also, when there is some error in the original promise, then that error is reported to the caller only if the promise is not cancelled. If the promise is cancelled then the cancellation is reported.   

```typescript
return {
    promise: wrappedPromise,
    cancel: () => {
        isCancelled.value = true;
    }
};
```   

Finally, we return an object, which has a converted promise as `promise` and another function called `cancel` which can be called, to cancel the promise when the component is destroyed.  

Now lets see how to use this function in a react component. Consider the following component.   

```typescript
const HelloComponent = () => {
    const [message, setMessage] = useState('');

    useEffect(async () => {
        const p = await axios.get('https://<someurlreturningthemessage>').then(d => d.data);
        const { promise, cancel } = cancellablePromise(p); // converting original promise to cancellable promise
        promise.then(d => {
            // Write your local state updates here
            setMessage(d);
        });
        return cancel;
    }, [setMessage]);

    return <div>
        <h2>{message}</h2>
    </div>;
};
```  

The above component just displays the message that is returned from an api call (a promise). Here, i have used an `axios` get call to get the message. The `useEffect` react hook is used to update the state of the local component based on any modifications in the list of dependencies that is passed as the second parameter, here it is just the `setMessage` function.

The main thing about the `useEffect` hook is that, the function that is returned with in the `useEffect` hook is called when the component is destroyed or removed from the component tree. As you can see, we return the `cancel` function that we got from the `cancellablePromise`. When react calls `cancel` function, the promise is set to cancelled (`value = true`). Now, the then block will not be called (which contains the state updates) if the component is destroyed.   

Refer [here](https://reactjs.org/docs/hooks-effect.html) for more detail about the `useEffect` hook.   

Now the `cancellablePromise` can be used inside any component where there is an api call or any other promise related stuffs.   

### Conclusion:
It is generally a good practice NOT to call the apis directly from the component. You may have to use some sort of state container libraries like redux, to do that for you and inject the result as props to your component. However, there may be exceptional cases where you need to call the apis directly (eg. Autofill). In such cases you can use the `cancellablePromise`.

__Hope you enjoyed! Happy Hacking!__