Why is the cancelledPromise pattern considered better than the isMounted() "antipattern" in React?
The key element here is that if (this.isMounted()) { setState(...) }
is an antipattern in general. It can lead to the supression of useful warnings, and so its appearance should be treated with suspicion since, in the majority of cases, it represents an opportunity to mask real issues. As such, even in cases where its behaviour is functionally the same as some other approach, that other approach is preferable.
In the case of API calls, it is entirely reasonable that you might want to ignore the results of a promise because it is no longer relevant. The use of a canceled promise syntactically and semantically ties the logic of whether to ignore the result specifically to the API call, which prevents any possibility of future developers accidentally making use of the code in another context and potentially supressing meaningful warnings.
Though the difference may be semantic, the semantics themselves have value to maintainability. In this case, the cancelable promise serves to structurally colocate concerns, attaching a behaviour that can be a problem in general to the specific situation in which it is okay.
isMounted
component method is deprecated. It cannot be used anymore in React 16.
There is no built-in _isMounted
state, it should be defined by the user in component lifecycle hooks. It's a matter of preference to some degree. The problem with it is that it requires promise chain to be coupled to component instance to be able to access this._isMounted
.
This is not a problem for cancellable promises. Even more, this pattern allows to actually cancel asynchronous process, e.g. Axios actually cancels XHR request during cancellation. Still, in order to make this work, this requires the entire promise chain to be cancellation-aware. This may be tedious since cancellation isn't supported in native promises, including async..await
.
Observables provide even more powerful ways to control the execution. There is a single point (a subscription) that is capable to cancel the entire chain by calling unsubscription function:
const unsubscribe = fetchData()
.mergeMap(asynchronousProcessing)
.mergeMap(yetAnotherAsynchronousProcessing)
.subscribe(data => this.setState(data));