Idomatic way to ignore `finally` block after cancelling network request In React, it is quite common for me to have something similar to the following.

async componentDidMount() {
    try {
        this.setState({ isLoading: true })
        const { data } = await axios.get('...')
        this.setState({ data })
    } catch(error) {
         handleError(error)
    } finally {
        this.setState({ isLoading: false })
    }
}


This is great because the cleanup code (isLoading: false) is DRY. That is, until I try to cancel network requests on unmount. That code would look like this:

async componentDidMount() {
    try {
        this.axiosCancelTokenSource = axios.CancelToken.source()
        this.setState({ isLoading: true })
        const { data } = await axios.get('...', {
            cancelToken: this.axiosCancelTokenSource.token,
        })
        this.setState({ data })
    } catch(error) {
         if (axios.isCancel(error)) return
         handleError(error)
    } finally {
        this.setState({ isLoading: false })
    }
}
componentWillUnmount() {
    if (this.axiosCancelTokenSource) this.axiosCancelTokenSource.cancel()
}


The problem with this is that it will setState after the component unmounts, which React will warn againts.

As far as I see it, these are my options for dealing with this:


Ignore the warning.
React gives a warning when you setState after unmount because it indicates a memory leak (in this case, the lingering network request, if not cancelled). If the network request is cancelled, there is still a setState after unmount, but just to set a flag. There is no more lingering network request. It should be safe to ignore the warning in this case, but it doesn't feel right.
Check what error was thrown in the finally block and add the same if statement as the catch block. This seems incredibly hacky and would require extra code to save the error from the catch block.
Check if the component is mounted in the finally block. This is also hacky and requires boilerplate code to update a this.isMounted flag.
Put the cleanup code at the end of try and after the condition in catch. This is not DRY. Humans are also very forgetful; I cannot count how many times I have forgotten to set isLoading = false in catch.
Define a cleanup() function before the try and call it in try and catch. This is a decent option, but requires extra function calls, making it harder to follow.


So far, it looks like the first or fifth options are best, depending on how much you care about seeing warning messages. Am I missing any good options?
Ignore the warning. React gives a warning when you setState after
  unmount because it indicates a memory leak (in this case, the
  lingering network request, if not canceled). If the network request
  is canceled, there is still a setState after unmount, but just to set
  a flag. There is no more lingering network request. It should be safe
  to ignore the warning in this case, but it doesn't feel right.


Bad idea. Thing is, by the time the request will resolve, setState might be triggered on an unmounted component. And warning turns to error.


  Check what error was thrown in the finally block and add the same if
  statement as the catch block. This seems incredibly hacky and would
  require extra code to save the error from the catch block.


Meh :\


  Check if the component is mounted in the finally block. This is also
  hacky and requires boilerplate code to update a this.isMounted flag.


It's an Anti-pattern




  Put the cleanup code at the end of try and after the condition in
  catch. This is not DRY. Humans are also very forgetful; I cannot count
  how many times I have forgotten to set isLoading = false in catch.


This


  Define a cleanup() function before the try and call it in try and
  catch. This is a decent option, but requires extra function calls,
  making it harder to follow.


And that, are pretty much taking the same turn with different gear.

Suggestion from React Blog

If you use ES6 promises, you may need to wrap your promise in order to make it cancelable.

const cancelablePromise = makeCancelable(
  new Promise(r =&gt; component.setState({...}))
);

cancelablePromise
  .promise
  .then(() =&gt; console.log('resolved'))
  .catch((reason) =&gt; console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise


Where makeCancelable was originally defined by @istarkov as:

const makeCancelable = (promise) =&gt; {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) =&gt; {
    promise.then(
      val =&gt; hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
      error =&gt; hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};