DEV Community

Discussion on: Cancel your promises when a component unmounts

Collapse
 
trusktr profile image
Joe Pea

Hello Robbie, neat trick. Does the promise that is never resolved or rejected get cleaned up (garbage collected), or does it stick around and leak memory?

Even if we're done with the component, will the engine be hanging on to each then handler waiting indefinitely to call setUser, which never happens?

One way to test this would be to re-render a component that uses this tool many times by incrementing a key prop on it like 100,000 times, and seeing if memory growth is garbage collected in Chrome Devtool's Performance tab.

What I do to ensure my Promises are cleaned up (so that dependent then or catch handlers aren't waiting indefinitely) is to reject them with a special error that I look out for in a catch, an error like class Cancellation extends Error, and when I reject the promise with that error the dependent then/catch handlers know the promise is settled (resolved or rejected) and in the case they receive a Cancellation rejection then they don't handle that error (or do something like onCancel), and otherwise handle other types of errors like regular errors.

Collapse
 
rodw1995 profile image
Robbie • Edited

Hi Joe, thank you for your comment and good point! I have not tested it but did some more research and I think you're probably right about the memory leak. There is also a discussion in a React issue on Github relevant to this. Maybe this can be a solution?

Collapse
 
trusktr profile image
Joe Pea

Hey, sorry for the late reply! That TrashablePromise implementation can still leak the promises if they never settle (same with the ideas mentioned in that GitHub issue). The only way to cancel a promise, is to reject it. But if we receive a promise and it is out of our control as to whether we can make it settle, then that TrashablePromise idea (or CancelablePromise from that GitHub issue) is the next best option, and in that case we can only hope the externally provided Promise will sometime settle.

Regarding the Fetch API, it has an Abort API which we can take advantage of to cancel the network operation, and we should make sure to reject any promises that we may have handed to any other code. It is expected that any other code handling promises should handle rejections (with .catch or try-catch). I think this is often overlooked. I have a lint rule in my project that throws an error on any code that doesn't handle promise rejection.

The downstream code can check the error to see if it is a special type of error that signifies cancellation. This is all up to the promise author to implement and document as part of their API documentation.

It would be neat if there was something higher level, but even if there was, ultimately the end developer would still need to explicitly stop async operations. I think this isn't as well-known as it should be, at least it seems to be an edge case that devs forget to handle until it becomes a problem (we are human after all 😃).