A canceleable promise utility which helps solve memory leak in asynchronous code in a react components which cannot be solved using [AbortController]() or other hooks which uses similar type of cancelling mechanisms.
AsyncAbort internally uses dont care policy
for the promise cancellation ie.. it won't stop the promises from settling but it stops callbacks attached from being called. It does so by detaching the loosely attached callbacks from promise and preventing memory references of these callbacks from being held by promise call which can cause memory leak if not.
Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 11 ✔ |
Using npm:
$ npm install async-abort
Using yarn:
$ yarn add async-abort
A React Component that will leak:
function ALeakyTodoComponent({ userId }) {
const [todos, setTodos] = useState([]);
const [failed, setFailed] = useState(false);
useEffect(() => {
// on mount
fetchTodosOfUser(userId)
.then((resp) => {
setTodos(resp.todos);
})
.catch((err) => {
setFailed(true);
});
}, [userId]);
return (<div>
{ failed && <FailedMsg/>}
{todos.map((todo) => (
<div>
<h1>{todo.title}</h1>
<p>{todo.description}</p>
</div>
))}
</div>)
}
Now what happens when the above component is mounted and umounted immediately
fetchTodosOfUser
is called on mount and it holds the references
to the setTodos
and setFailed
callbacksUsing AsyncAbort to prevent this leak:
import AsyncAbort from 'async-abort';
function ANonLeakyTodoComponent({ userId }) {
const [todos, setTodos] = useState([]);
const [failed, setFailed] = useState(false);
useEffect(() => {
// on mount
const cancel = new AsyncAbort(fetchTodosOfUser, [userId])
.then((resp) => {
setTodos(resp.todos);
})
.catch((err) => {
setFailed(true);
})
.call();
return () => {
cancel();
}
}, [userId]);
return (<div>
{ failed && <FailedMsg/>}
{todos.map((todo) => (
<div>
<h1>{todo.title}</h1>
<p>{todo.description}</p>
</div>
))}
</div>)
}
Now what happens when the above component is mounted and umounted immediately
fetchTodosOfUser
is called on mount through AsyncAbortcancel()
is called in cleanup method of hook this will remove the references
of then,catch,finally callbacks which are attached to the fetchTodoOfUser
note cancel won't stop promise from being settlingTo get TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const AsyncAbort = require('async-abort').default;
// AsyncAbort.<method> will now provide autocomplete and parameter typings
Example for calling an Async Function
or Any Promise returning Function
import AsyncAbort from 'async-abort';
async function anAsyncFunc(arg1, arg2) {
// does some operations
// throws an error or returns a value
}
new AsyncAbort(anAsyncFunc, [arg1Value, arg2Value])
.then((resp) => {
// code when promise resolves
}).catch((err) => {
// code when promise rejects
}).finally(() => {
// similar to finally block
}).call();
function somePromiseReturningFunc(arg1, arg2) {
// returns a promise
return new Promise((resolve, reject) => {
// either rejects or resolves
});
}
new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
.then((resp) => {
// code when promise resolves
}).catch((err) => {
// code when promise rejects
}).finally(() => {
// similar to finally block
}).call();
Action Blocks can be omitted
// then and finally are omitted
new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
.catch((err) => {
...
}).call();
// finally is omitted
new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
.then((val) => {
...
}).catch((resp) => {
...
}).call();
note : .call()
is necessary to call the async function that is passed
Cancelling execution of Action blocks
const cancel = new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
.then((val) => {
...
}).catch((resp) => {
...
}).call();
// to prevent execution of Action blocks (then, catch, finally)
cancel();