1 | <a><img src="https://img.shields.io/bundlephobia/minzip/async-af.svg?style=for-the-badge&label=size&colorB=466EF1" alt="npm package size (min + gzip)"></a>
|
2 | <a href="https://www.npmjs.com/package/async-af" target=_blank><img src="https://img.shields.io/npm/v/async-af.svg?style=for-the-badge&colorB=cb3837" alt="npm version"></a>
|
3 | <a href="https://yarnpkg.com/en/package/async-af" target=_blank><img src="https://img.shields.io/npm/v/async-af.svg?label=yarn&style=for-the-badge&colorB=2c8ebb" alt="yarn version"></a>
|
4 | <a href="https://unpkg.com/async-af/" target=_blank><img src="https://img.shields.io/npm/v/async-af.svg?style=for-the-badge&colorB=ffcc2f&label=unpkg" alt="unpkg version"></a>
|
5 | <a href="https://codecov.io/gh/AsyncAF/AsyncAF" target=_blank><img src="https://img.shields.io/codecov/c/github/AsyncAF/AsyncAF.svg?style=for-the-badge&label=codecov&colorB=brightgreen" alt=codecov></a>
|
6 | <a href="https://github.com/AsyncAF/AsyncAF/blob/master/README.md#license"><img src="https://img.shields.io/npm/l/async-af.svg?style=for-the-badge&colorB=aaaaaa" alt="MIT License"></a>
|
7 |
|
8 | <p align=center><a href="https://async-af.js.org" target=_blank><img src="https://cdn.rawgit.com/AsyncAF/AsyncAF/1ce388a7/docs/custom/assets/async-af-logo.png"></a></p>
|
9 |
|
10 | <br>
|
11 |
|
12 | Working with promises or async/await?
|
13 |
|
14 | Use AsyncAF to transform your code into beautiful asynchronous JavaScript chains, with methods similar to the ones we all know (and love! 😍) such as `map`, `forEach`, `filter`, `reduce`, and more.
|
15 |
|
16 | <a href="https://async-af.js.org/AsyncAF" target=_blank>
|
17 | Check out the docs
|
18 | </a> for all available methods. 💙
|
19 | <br>
|
20 |
|
21 | ## Usage
|
22 |
|
23 | ```js
|
24 | const AsyncAF = require('async-af');
|
25 |
|
26 | function getActiveUsers(userIds) {
|
27 | return AsyncAF(userIds)
|
28 | // map user ids to user objects with an async function
|
29 | .mapAF(async userId => {
|
30 | const user = await fetch(`fake-game-api/users/${userId}`);
|
31 | return user.json();
|
32 | })
|
33 | // filter by active users
|
34 | .filterAF(user => user.isActive);
|
35 | }
|
36 | ```
|
37 |
|
38 | AsyncAF methods are await-able and then-able.
|
39 |
|
40 | ```js
|
41 | async function sendMsgToActiveUsers(msg) {
|
42 | const activeUsers = await getActiveUsers([1, 2, 3]);
|
43 | // send each active user a msg in series
|
44 | await AsyncAF(activeUsers).series.forEachAF(async ({id}) => {
|
45 | await sendMsg(id, msg); // hypothetical msg service that's rate-limited
|
46 | });
|
47 | console.log('message sent!');
|
48 | }
|
49 |
|
50 | function doSomethingElseWithActiveUsers() {
|
51 | return getActiveUsers([1, 2, 3]).then(activeUsers => {
|
52 | // ... do something else
|
53 | });
|
54 | }
|
55 | ```
|
56 |
|
57 | If a `Promise` is passed into <a href="https://async-af.js.org/AsyncAF" target=_blank>`AsyncAF`</a>, it will be settled before a method processes it.
|
58 |
|
59 | ```js
|
60 | const userMsg = Promise.resolve('I\'m [restricted word] AF right now')
|
61 |
|
62 | const msgContainsBadWord = (msg, word = '[restricted word]') => AsyncAF(msg).includesAF(word);
|
63 |
|
64 | msgContainsBadWord(userMsg); // resolves to true
|
65 | ```
|
66 |
|
67 | `Array` methods will settle any promises in an array before processing them.
|
68 |
|
69 | ```js
|
70 | const findHighScoringUser = () => AsyncAF([
|
71 | fetch('fake-game-api/users/1').then(user => user.json()), // {id: 1, name: Aiden, score: 9001, ...}
|
72 | {id: 2, name: 'Bill', score: 3600, /* ... */},
|
73 | {id: 3, name: 'Cari', score: 5800, /* ... */},
|
74 | ])
|
75 | .findAF(({score}) => score > 9000);
|
76 |
|
77 | findHighScoringUser(); // resolves to {id: 1, name: Aiden, score: 9001, ...}
|
78 | ```
|
79 |
|
80 | **Note**: All `'AF'` methods have an `'AF-less'` alias so you can choose whether or not to make it obvious that they're AsyncAF methods.
|
81 |
|
82 | For example:
|
83 |
|
84 | ```js
|
85 | const promises = [1, 2, 3].map(n => Promise.resolve(n));
|
86 |
|
87 | AsyncAF(promises).map(n => n * 2).filter(n => n !== 4).forEach(n => console.log(n));
|
88 | // or
|
89 | AsyncAF(promises).mapAF(n => n * 2).filterAF(n => n !== 4).forEachAF(n => console.log(n));
|
90 | // both log 2 then 6
|
91 | ```
|
92 |
|
93 | ## Installation 💾
|
94 |
|
95 | Easy peasy, just
|
96 |
|
97 | `$ npm install --save async-af`,
|
98 |
|
99 | right?
|
100 |
|
101 | ⚠️ Not so fast; there's actually several ways to include AsyncAF in your project/production site from easy to more complex:
|
102 |
|
103 | <details><summary>Easy 👍</summary><br>
|
104 | 🔹 <strong>npm:</strong> <code>$ npm install --save async-af</code>
|
105 |
|
106 | 🔸 <strong>yarn:</strong> <code>$ yarn add async-af</code>
|
107 |
|
108 | 🔹 <strong>bower:</strong> <code>async-af</code> is no longer published to bower. To continue using it with bower, look into <a href="https://github.com/mjeanroy/bower-npm-resolver" target=_blank><code>bower-npm-resolver</code></a>.
|
109 |
|
110 | 🔸 <strong>cdn:</strong> See the table for which script tag to use:
|
111 |
|
112 | <table align=left><th>mode</th><th>browsers</th><th>script tag</th>
|
113 | <tr><td>development</td><td>modern (ES6+)</td><td><code class=language-html><script src="https://unpkg.com/async-af/index.js"></script></code></td></tr>
|
114 | <tr><td>development</td><td>legacy (ES5+)</td><td><code class=language-html><script src="https://unpkg.com/async-af/legacy/index.js"></script></code></td></tr>
|
115 | <tr><td>production</td><td>modern (ES6+)</td><td><code class=language-html><script src="https://unpkg.com/async-af/min.js"></script></code></td></tr>
|
116 | <tr><td>production</td><td>legacy (ES5+)</td><td><code class=language-html><script src="https://unpkg.com/async-af/legacy/min.js"></script></code></td></tr>
|
117 | </table><br>
|
118 | </details>
|
119 | <br>
|
120 | <details><summary>More Complex 🤔</summary><br>
|
121 |
|
122 | 🔹 <strong>scoped packages:</strong>
|
123 |
|
124 | >Instead of pulling in the entire AsyncAF library, you can install smaller standalone packages for each of the AsyncAF methods you intend to use; for example, <code>@async-af/map</code> and/or <code>@async-af/filter</code>; see further instructions in the documentation for <a href="https://async-af.js.org/AsyncAfWrapper" target=_blank>AsyncAfWrapper</a> and <a href="https://async-af.js.org/AsyncAfWrapper#use" target=_blank>AsyncAfWrapper.use</a>.
|
125 |
|
126 | 🔸 <strong>scoped packages + `babel-plugin-transform-imports`:</strong>
|
127 |
|
128 | >If you use more than a few AsyncAF scoped packages in a file, you might start to build a wall of `import` statements to pull them all in. If this is an eyesore for you, look into <a href="https://www.npmjs.com/package/babel-plugin-transform-imports" target="_blank"><code>babel-plugin-transform-imports</code></a> and condense that ugly wall down to a single `import` statement! See <a href="https://async-af.js.org/tutorial-TOO_MANY_IMPORTS" target=_blank>Wrapper/Use: Too Many 🤬 Imports!?</a> for a tutorial.
|
129 |
|
130 | 🔹 <strong>es modules:</strong>
|
131 |
|
132 | >AsyncAF as well as its scoped packages are also published as es modules. This gives an opportunity to conditionally load `async-af` with ES6+ features in modern browsers and `async-af` with ES5-compatible features in legacy browsers.
|
133 | >
|
134 | >Using the cdn scripts as an example:
|
135 | >
|
136 | ><pre class=prettyprint>
|
137 | ><code><code class=language-html><script type="module" src="https://unpkg.com/async-af/esm/index.js"></script></code>
|
138 | ><code class=language-html><script nomodule src="https://unpkg.com/async-af/legacy/index.js"></script></code></code></pre>
|
139 | >
|
140 | >or minimized for production:
|
141 | >
|
142 | ><pre class=prettyprint>
|
143 | ><code><code class=language-html><script type="module" src="https://unpkg.com/async-af/esm/min.js"></script></code>
|
144 | ><code class=language-html><script nomodule src="https://unpkg.com/async-af/legacy/min.js"></script></code></code></pre>
|
145 |
|
146 | >The script with <code class="language-html prettyprint"><script type="module"></code> will load in any browser capable of loading es modules, while the script with <code class="language-html prettyprint"><script nomodule></code> will act as a fallback for legacy browsers.
|
147 | >
|
148 | >See <a href="https://philipwalton.com/articles/deploying-es2015-code-in-production-today/" target=_blank>here</a> and <a href="https://jakearchibald.com/2017/es-modules-in-browsers/" target=_blank>here</a> for further reading on this strategy.
|
149 | </details>
|
150 | <br>
|
151 |
|
152 | ## A couple notes on performance 🚀
|
153 |
|
154 | #### Built on Promises
|
155 |
|
156 | Despite AsyncAF's name (Async/Await Fun), its source code is written entirely without the use of `async/await`. Its chainable asynchronous JavaScript methods _are_, however, highly useful when _your_ code makes use of `async/await` or `Promises`. This is important for performance because transpiling an `async function` with babel currently <a href="https://medium.com/@bluepnume/even-with-async-await-you-probably-still-need-promises-9b259854c161" target=_blank>results in some loooong code</a> due to pulling in things like <a href="https://facebook.github.io/regenerator/" target=_blank>Facebook's regenerator</a> and others to make it work.
|
157 |
|
158 | Because AsyncAF instead runs your code with Promises behind the scenes, there's no need to transpile `async/await` in its ES6 or ES5-compatible distributions. This boils down to much smaller bundles when compared to an equivalent async library written _with_ `async/await`.
|
159 |
|
160 | #### Use <a href="https://async-af.js.org/AsyncAF#series" target=_blank>`series`</a> wisely
|
161 |
|
162 | The majority of AsyncAF's `Array` methods process promises in parallel by default. However, many methods have an option to process promises in series as well. You can tell AsyncAF to process promises in series within the next method invocation by setting a flag with `series` or its alias `io` (in order). See the documentation for <a href="https://async-af.js.org/AsyncAF#series" target=_blank>`series`</a> for a full list of methods this works on.
|
163 |
|
164 | In some cases, the time it takes to resolve an AsyncAF expression won't differ; for example:
|
165 |
|
166 | ```js
|
167 | import AsyncAF, {logAF} from 'async-af';
|
168 | import delay from 'delay';
|
169 |
|
170 | logAF.options({label: false});
|
171 |
|
172 | const bools = [
|
173 | () => delay(3000, {value: true}),
|
174 | () => delay(2000, {value: false}),
|
175 | () => delay(1000, {value: false}),
|
176 | ];
|
177 |
|
178 | logAF('parallel', AsyncAF(bools).someAF(n => n()));
|
179 | logAF('series', AsyncAF(bools).series.someAF(n => n()));
|
180 |
|
181 | // series true
|
182 | // in 3.000 secs
|
183 | // parallel true
|
184 | // in 3.000 secs
|
185 | ```
|
186 |
|
187 | Other times, processing promises in parallel will be faster:
|
188 |
|
189 | ```js
|
190 | const bools = [
|
191 | () => delay(3000, {value: false}),
|
192 | () => delay(2000, {value: true}),
|
193 | () => delay(1000, {value: false}),
|
194 | ];
|
195 |
|
196 | logAF('parallel', AsyncAF(bools).someAF(n => n()));
|
197 | logAF('series', AsyncAF(bools).series.someAF(n => n()));
|
198 |
|
199 | // parallel true
|
200 | // in 3.000 secs
|
201 | // series true
|
202 | // in 5.000 secs
|
203 | ```
|
204 |
|
205 | And yet other times, processing promises in series will be faster:
|
206 |
|
207 | ```js
|
208 | const bools = [
|
209 | () => delay(3000, {value: true}),
|
210 | () => delay(4000, {value: false}),
|
211 | () => delay(5000, {value: false}),
|
212 | ];
|
213 |
|
214 | logAF('parallel', AsyncAF(bools).someAF(n => n()));
|
215 | logAF('series', AsyncAF(bools).series.someAF(n => n()));
|
216 |
|
217 | // series true
|
218 | // in 3.000 secs
|
219 | // parallel true
|
220 | // in 5.000 secs
|
221 | ```
|
222 |
|
223 | Being cognizant of when to use `series` vs. when to rely on the default parallel behavior can help increase the performance of your asynchronous code.
|
224 |
|
225 | Another use case for `series` might be if you're sending multiple requests to a rate-limited API. In that case you may not want to throw a ton of parallel requests at the API, but rather wait for each one in series.
|
226 |
|
227 | <div>Love AsyncAF? <a href="https://github.com/AsyncAF/AsyncAF"><img src="https://badgen.net/badge//star/blue?icon=github" alt="star it on GitHub"></a></div>
|
228 |
|
229 | See something to improve? [File an issue](https://github.com/AsyncAF/AsyncAF/issues) or
|
230 | [see how to contribute](https://github.com/AsyncAF/AsyncAF/blob/master/CONTRIBUTING.md).💙
|
231 |
|
232 | ## License
|
233 | AsyncAF is licensed under the <a href="https://choosealicense.com/licenses/mit/" target=_blank>MIT License</a>, so you can pretty much use it however you want
|
234 |
|
235 | (but [click here](https://github.com/AsyncAF/AsyncAF/blob/master/LICENSE) or <a href="https://app.fossa.io/projects/git%2Bgithub.com%2FAsyncAF%2FAsyncAF/refs/branch/master/" target=_blank>here</a> to get into specifics).
|
236 |
|
237 | <a href="https://app.fossa.io/projects/git%2Bgithub.com%2FAsyncAF%2FAsyncAF/refs/branch/master/" target=_blank><img src="https://app.fossa.io/api/projects/git%2Bgithub.com%2FAsyncAF%2FAsyncAF.svg?type=large" alt="FOSSA Status"></a>
|