1 | # async-abort
|
2 |
|
3 | [![npm version](https://img.shields.io/npm/v/async-abort.svg?style=flat-square)](https://www.npmjs.org/package/async-abort)
|
4 | [![install size](https://packagephobia.now.sh/badge?p=async-abort)](https://packagephobia.now.sh/result?p=async-abort)
|
5 | [![bundle size](https://badgen.net/bundlephobia/min/async-abort)](https://badgen.net/bundlephobia/min/async-abort)
|
6 | [![npm downloads](https://img.shields.io/npm/dm/async-abort.svg?style=flat-square)](http://npm-stat.com/charts.html?package=async-abort)
|
7 |
|
8 |
|
9 |
|
10 | 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.
|
11 | 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.
|
12 |
|
13 | ## Table of Contents
|
14 |
|
15 | - [Features](#features)
|
16 | - [Browser Support](#browser-support)
|
17 | - [Installing](#installing)
|
18 | - [Preventing Memory leaks in React Component](#preventing-memory-leaks-in-react-component)
|
19 | - [Other Examples](#other-examples)
|
20 | - [Resources](#resources)
|
21 | - [Credits](#credits)
|
22 | - [License](#license)
|
23 |
|
24 | ## Features
|
25 |
|
26 | - Ability to prevent execution of actions (then, catch, finally blocks) that happen when a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) returned from async function settles.
|
27 | - Useful in Preventing Memory leaks resulting in asynchronous code in React components
|
28 | - works with any kind of promises (native or bluebord or any other polyfills)
|
29 | ## Browser Support
|
30 |
|
31 | ![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/src/safari/safari_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/src/opera/opera_48x48.png) | ![Edge](https://raw.github.com/alrra/browser-logos/master/src/edge/edge_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png) |
|
32 | --- | --- | --- | --- | --- | --- |
|
33 | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 11 ✔ |
|
34 |
|
35 | ## Installing
|
36 |
|
37 | Using npm:
|
38 |
|
39 | ```bash
|
40 | $ npm install async-abort
|
41 | ```
|
42 |
|
43 | Using yarn:
|
44 |
|
45 | ```bash
|
46 | $ yarn add async-abort
|
47 | ```
|
48 |
|
49 |
|
50 | ## Preventing Memory leaks in React Component
|
51 | A React Component that will leak:
|
52 | ```javascript
|
53 |
|
54 | function ALeakyTodoComponent({ userId }) {
|
55 | const [todos, setTodos] = useState([]);
|
56 | const [failed, setFailed] = useState(false);
|
57 |
|
58 | useEffect(() => {
|
59 | // on mount
|
60 | fetchTodosOfUser(userId)
|
61 | .then((resp) => {
|
62 | setTodos(resp.todos);
|
63 | })
|
64 | .catch((err) => {
|
65 | setFailed(true);
|
66 | });
|
67 | }, [userId]);
|
68 |
|
69 | return (<div>
|
70 | { failed && <FailedMsg/>}
|
71 | {todos.map((todo) => (
|
72 | <div>
|
73 | <h1>{todo.title}</h1>
|
74 | <p>{todo.description}</p>
|
75 | </div>
|
76 | ))}
|
77 | </div>)
|
78 | }
|
79 | ```
|
80 |
|
81 | Now what happens when the above component is mounted and umounted immediately
|
82 | 1. the `fetchTodosOfUser` is called on mount and it holds the references
|
83 | to the `setTodos` and `setFailed` callbacks
|
84 | 2. while the promise is still being settled the component will unmount,
|
85 | in order for component to completely unmount and garbage collected, all the references must be cleared.
|
86 | but the promise holds the references of the setState callbacks untill it settles
|
87 | 3. this will stop the component from garbage collected and leads to memory leak
|
88 | 4. even if you use AbortController or some flags to
|
89 | stop setting the state in your code, the references are still attached and it will not prevent the memory leak
|
90 |
|
91 | Using AsyncAbort to prevent this leak:
|
92 |
|
93 | ```javascript
|
94 |
|
95 | import AsyncAbort from 'async-abort';
|
96 |
|
97 | function ANonLeakyTodoComponent({ userId }) {
|
98 | const [todos, setTodos] = useState([]);
|
99 | const [failed, setFailed] = useState(false);
|
100 | useEffect(() => {
|
101 | // on mount
|
102 | const cancel = new AsyncAbort(fetchTodosOfUser, [userId])
|
103 | .then((resp) => {
|
104 | setTodos(resp.todos);
|
105 | })
|
106 | .catch((err) => {
|
107 | setFailed(true);
|
108 | })
|
109 | .call();
|
110 | return () => {
|
111 | cancel();
|
112 | }
|
113 | }, [userId]);
|
114 |
|
115 | return (<div>
|
116 | { failed && <FailedMsg/>}
|
117 | {todos.map((todo) => (
|
118 | <div>
|
119 | <h1>{todo.title}</h1>
|
120 | <p>{todo.description}</p>
|
121 | </div>
|
122 | ))}
|
123 | </div>)
|
124 | }
|
125 | ```
|
126 |
|
127 | Now what happens when the above component is mounted and umounted immediately
|
128 | 1. the `fetchTodosOfUser` is called on mount through AsyncAbort
|
129 | 2. the component gets unmounted while the promise is still being settled and
|
130 | `cancel()` is called in cleanup method of hook this will remove the references
|
131 | of then,catch,finally callbacks which are attached to the `fetchTodoOfUser`
|
132 | **note** cancel won't stop promise from being settling
|
133 | 3. after calling cancel() no more references of component are held, the component is garbage collected.
|
134 | 4. thus no more memory leaks
|
135 |
|
136 |
|
137 | ## Other Examples
|
138 |
|
139 | ### note: CommonJS usage
|
140 | To get TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with `require()` use the following approach:
|
141 |
|
142 | ```js
|
143 | const AsyncAbort = require('async-abort').default;
|
144 |
|
145 | // AsyncAbort.<method> will now provide autocomplete and parameter typings
|
146 | ```
|
147 |
|
148 | Example for calling an `Async Function` or `Any Promise returning Function`
|
149 |
|
150 | ```js
|
151 | import AsyncAbort from 'async-abort';
|
152 |
|
153 | async function anAsyncFunc(arg1, arg2) {
|
154 | // does some operations
|
155 | // throws an error or returns a value
|
156 | }
|
157 |
|
158 | new AsyncAbort(anAsyncFunc, [arg1Value, arg2Value])
|
159 | .then((resp) => {
|
160 | // code when promise resolves
|
161 | }).catch((err) => {
|
162 | // code when promise rejects
|
163 | }).finally(() => {
|
164 | // similar to finally block
|
165 | }).call();
|
166 |
|
167 | function somePromiseReturningFunc(arg1, arg2) {
|
168 | // returns a promise
|
169 | return new Promise((resolve, reject) => {
|
170 | // either rejects or resolves
|
171 | });
|
172 | }
|
173 |
|
174 | new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
|
175 | .then((resp) => {
|
176 | // code when promise resolves
|
177 | }).catch((err) => {
|
178 | // code when promise rejects
|
179 | }).finally(() => {
|
180 | // similar to finally block
|
181 | }).call();
|
182 |
|
183 | ```
|
184 | Action Blocks can be omitted
|
185 |
|
186 | ```js
|
187 | // then and finally are omitted
|
188 | new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
|
189 | .catch((err) => {
|
190 | ...
|
191 | }).call();
|
192 |
|
193 | // finally is omitted
|
194 | new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
|
195 | .then((val) => {
|
196 | ...
|
197 | }).catch((resp) => {
|
198 | ...
|
199 | }).call();
|
200 |
|
201 | ```
|
202 | **note** : `.call()` is necessary to call the async function that is passed
|
203 |
|
204 | Cancelling execution of Action blocks
|
205 |
|
206 | ```js
|
207 |
|
208 | const cancel = new AsyncAbort(somePromiseReturningFunc, [arg1Value, arg2Value])
|
209 | .then((val) => {
|
210 | ...
|
211 | }).catch((resp) => {
|
212 | ...
|
213 | }).call();
|
214 |
|
215 | // to prevent execution of Action blocks (then, catch, finally)
|
216 | cancel();
|
217 | ```
|
218 |
|
219 | ## Resources
|
220 |
|
221 | * [Changelog](https://github.com/mohanteja1/async-abort/blob/master/CHANGELOG.md)
|
222 |
|
223 |
|
224 | ## Credits
|
225 | - to setup the environment for building this npm package I used code from this [repo](https://github.com/GeorgianStan/framework-for-building-libraries)
|
226 | - used readme file from [axios](https://github.com/axios/axios) project as a template for this readme file
|
227 | ## License
|
228 |
|
229 | [MIT](./LICENSE)
|