1 | # promise-toolbox [![Build Status](https://travis-ci.org/JsCommunity/promise-toolbox.png?branch=master)](https://travis-ci.org/JsCommunity/promise-toolbox)
|
2 |
|
3 | > Essential utils for promises.
|
4 |
|
5 | Features:
|
6 |
|
7 | - small (< 150 KB with all dependencies, < 5 KB with gzip)
|
8 | - nice with ES2015 / ES2016 syntax
|
9 |
|
10 | ## Install
|
11 |
|
12 | Installation of the [npm package](https://npmjs.org/package/promise-toolbox):
|
13 |
|
14 | ```
|
15 | > npm install --save promise-toolbox
|
16 | ```
|
17 |
|
18 | ## Usage
|
19 |
|
20 | If your environment may not natively support promises, you should use a polyfill such as [native-promise-only](https://github.com/getify/native-promise-only).
|
21 |
|
22 | On Node, if you want to use a specific promise implementation,
|
23 | [Bluebird](http://bluebirdjs.com/docs/why-bluebird.html) for instance
|
24 | to have better performance, you can override the global Promise
|
25 | variable:
|
26 |
|
27 | ```js
|
28 | global.Promise = require('bluebird')
|
29 | ```
|
30 |
|
31 | > Note that it should only be done at the application level, never in
|
32 | > a library!
|
33 |
|
34 | ### Decorators
|
35 |
|
36 | #### cancellable
|
37 |
|
38 | > Make your async functions cancellable.
|
39 |
|
40 | ```js
|
41 | import { cancellable } from 'promise-toolbox'
|
42 |
|
43 | const asyncFunction = cancellable(async function (cancellation, a, b) {
|
44 | cancellation.catch(() => {
|
45 | // do stuff regarding the cancellation request.
|
46 | })
|
47 |
|
48 | // do other stuff.
|
49 | })
|
50 |
|
51 | const promise = asyncFunction('foo', 'bar')
|
52 | promise.cancel()
|
53 | ```
|
54 |
|
55 | If the function is a method of a class or an object, you can use
|
56 | `cancellable` as a decorator:
|
57 |
|
58 | ```js
|
59 | class MyClass {
|
60 | @cancellable
|
61 | async asyncMethod (cancellation, a, b) {
|
62 | cancellation.catch(() => {
|
63 | // do stuff regarding the cancellation request.
|
64 | })
|
65 |
|
66 | // do other stuff.
|
67 | }
|
68 | }
|
69 | ```
|
70 |
|
71 | ### Functions
|
72 |
|
73 | #### defer()
|
74 |
|
75 | > Discouraged but sometimes necessary way to create a promise.
|
76 |
|
77 | ```js
|
78 | import { defer } from 'promise-toolbox'
|
79 |
|
80 | const { promise, resolve } = defer()
|
81 |
|
82 | promise.then(value => {
|
83 | console.log(value)
|
84 | })
|
85 |
|
86 | resolve(3)
|
87 | ```
|
88 |
|
89 | #### fromCallback(cb => fn(arg1, ..., argn, cb))
|
90 |
|
91 | > Easiest and most efficient way to promisify a function call.
|
92 |
|
93 | ```js
|
94 | import { fromCallback } from 'promise-toolbox'
|
95 |
|
96 | fromCallback(cb => fs.readFile('foo.txt', cb))
|
97 | .then(content => {
|
98 | console.log(content)
|
99 | })
|
100 | ```
|
101 |
|
102 | #### isPromise(value)
|
103 |
|
104 | ```js
|
105 | import { isPromise } from 'promise-toolbox'
|
106 |
|
107 | if (isPromise(foo())) {
|
108 | console.log('foo() returns a promise')
|
109 | }
|
110 | ```
|
111 |
|
112 | #### join(p1, ..., pn, cb) / join([p1, ..., pn], cb)
|
113 |
|
114 | > Easiest and most efficient way to wait for a fixed amount of
|
115 | > promises.
|
116 |
|
117 | ```js
|
118 | import { join } from 'promise-toolbox'
|
119 |
|
120 | join(getPictures(), getComments(), getTweets(), (pictures, comments, tweets) => {
|
121 | console.log(`in total: ${pictures.length + comments.length + tweets.length}`)
|
122 | })
|
123 | ```
|
124 |
|
125 | #### promisify(fn, [ context ]) / promisifyAll(obj)
|
126 |
|
127 | > Creates async functions taking node-style callbacks, create new ones
|
128 | > returning promises.
|
129 |
|
130 | ```js
|
131 | import fs from 'fs'
|
132 | import { promisify, promisifyAll } from 'promise-toolbox'
|
133 |
|
134 | // Promisify a single function.
|
135 | //
|
136 | // If possible, the function name is kept and the new length is set.
|
137 | const readFile = promisify(fs.readFile)
|
138 |
|
139 | // Or all functions (own or inherited) exposed on a object.
|
140 | const fsPromise = promisifyAll(fs)
|
141 |
|
142 | readFile(__filename).then(content => console.log(content))
|
143 |
|
144 | fsPromise.readFile(__filename).then(content => console.log(content))
|
145 | ```
|
146 |
|
147 | ### Pseudo-methods
|
148 |
|
149 | This function can be used as if they were methods, i.e. by passing the
|
150 | promise (or promises) as the context.
|
151 |
|
152 | This is extremely easy using [ES2016's bind syntax](https://github.com/zenparsing/es-function-bind).
|
153 |
|
154 | ```js
|
155 | const promises = [
|
156 | Promise.resolve('foo'),
|
157 | Promise.resolve('bar')
|
158 | ]
|
159 |
|
160 | promises::all().then(values => {
|
161 | console.log(values)
|
162 | })
|
163 | // → [ 'foo', 'bar' ]
|
164 | ```
|
165 |
|
166 | If you are still an older version of ECMAScript, fear not: simply pass
|
167 | the promise (or promises) as the first argument of the `.call()`
|
168 | method:
|
169 |
|
170 | ```js
|
171 | var promises = [
|
172 | Promise.resolve('foo'),
|
173 | Promise.resolve('bar')
|
174 | ]
|
175 |
|
176 | all.call(promises).then(function (values) {
|
177 | console.log(values)
|
178 | })
|
179 | // → [ 'foo', 'bar' ]
|
180 | ```
|
181 |
|
182 | #### promises::all([ mapper ])
|
183 |
|
184 | > Waits for all promises of a collection to be resolved.
|
185 | >
|
186 | > Contrary to the standard `Promise.all()`, this function works also
|
187 | > with objects.
|
188 |
|
189 | ```js
|
190 | import { all } from 'promise-toolbox'
|
191 |
|
192 | [
|
193 | Promise.resolve('foo'),
|
194 | Promise.resolve('bar')
|
195 | ]::all().then(value => {
|
196 | console.log(value)
|
197 | // → ['foo', 'bar']
|
198 | })
|
199 |
|
200 | {
|
201 | foo: Promise.resolve('foo'),
|
202 | bar: Promise.resolve('bar')
|
203 | }::all().then(value => {
|
204 | console.log(value)
|
205 | // → {
|
206 | // foo: 'foo',
|
207 | // bar: 'bar'
|
208 | // }
|
209 | })
|
210 | ```
|
211 |
|
212 | #### promise::asCallback(cb)
|
213 |
|
214 | > Register a node-style callback on this promise.
|
215 |
|
216 | ```js
|
217 | import { asCallback } from 'promise-toolbox'
|
218 |
|
219 | // This function can be used either with node-style callbacks or with
|
220 | // promises.
|
221 | function getDataFor (input, callback) {
|
222 | return dataFromDataBase(input)::asCallback(callback)
|
223 | }
|
224 | ```
|
225 |
|
226 | #### promise::catchPlus(predicate, cb)
|
227 |
|
228 | > Similar to `Promise#catch()` but:
|
229 | >
|
230 | > - support predicates
|
231 | > - do not catch `ReferenceError`, `SyntaxError` or `TypeError` unless
|
232 | > they match a predicate because they are usually programmer errors
|
233 | > and should be handled separately.
|
234 |
|
235 | ```js
|
236 | somePromise.then(() => {
|
237 | return a.b.c.d()
|
238 | })::catchPlus(TypeError, ReferenceError, reason => {
|
239 | // Will end up here on programmer error
|
240 | })::catchPlus(NetworkError, TimeoutError, reason => {
|
241 | // Will end up here on expected everyday network errors
|
242 | })::catchPlus(reason => {
|
243 | // Catch any unexpected errors
|
244 | })
|
245 | ```
|
246 |
|
247 | #### promise::delay(ms)
|
248 |
|
249 | > Delays the resolution of a promise by `ms` milliseconds.
|
250 | >
|
251 | > Note: the rejection is not delayed.
|
252 |
|
253 | ```js
|
254 | console.log(await Promise.resolve('500ms passed')::delay(500))
|
255 | // → 500 ms passed
|
256 | ```
|
257 |
|
258 | Also works with a value:
|
259 |
|
260 | ```js
|
261 | console.log(await delay.call('500ms passed', 500))
|
262 | // → 500 ms passed
|
263 | ```
|
264 |
|
265 | #### promises::forEach(cb)
|
266 |
|
267 | > Iterates in order over a collection of promises waiting for each of
|
268 | > them to be resolved.
|
269 |
|
270 | ```js
|
271 | [
|
272 | Promise.resolve('foo'),
|
273 | Promise.resolve('bar'),
|
274 | ]::forEach(value => {
|
275 | console.log(value)
|
276 | })
|
277 | // →
|
278 | // foo
|
279 | // bar
|
280 | ```
|
281 |
|
282 | #### promise::lastly(cb)
|
283 |
|
284 | > Execute a handler regardless of the promise fate. Similar to the
|
285 | > `finally` block in synchronous codes.
|
286 | >
|
287 | > The resolution value or rejection reason of the initial promise is
|
288 | > forwarded unless the callback rejects.
|
289 |
|
290 | ```js
|
291 | import { lastly } from 'promise-toolbox'
|
292 |
|
293 | function ajaxGetAsync (url) {
|
294 | return new Promise((resolve, reject) => {
|
295 | const xhr = new XMLHttpRequest
|
296 | xhr.addEventListener('error', reject)
|
297 | xhr.addEventListener('load', resolve)
|
298 | xhr.open('GET', url)
|
299 | xhr.send(null)
|
300 | })::lastly(() => {
|
301 | $('#ajax-loader-animation').hide()
|
302 | })
|
303 | }
|
304 | ```
|
305 |
|
306 | #### promise::reflect()
|
307 |
|
308 | > Returns a promise which resolves to an objects which reflects the
|
309 | > resolution of this promise.
|
310 |
|
311 | ```js
|
312 | import { reflect } from 'promise-toolbox'
|
313 |
|
314 | const inspection = await promise::reflect()
|
315 |
|
316 | if (inspection.isFulfilled()) {
|
317 | console.log(inspection.value())
|
318 | } else {
|
319 | console.error(inspection.reason())
|
320 | }
|
321 | ```
|
322 |
|
323 | #### promises::some(count)
|
324 |
|
325 | > Waits for `count` promises in a collection to be resolved.
|
326 |
|
327 | ```js
|
328 | import { some } from 'promise-toolbox'
|
329 |
|
330 | const [ first, seconds ] = await [
|
331 | ping('ns1.example.org'),
|
332 | ping('ns2.example.org'),
|
333 | ping('ns3.example.org'),
|
334 | ping('ns4.example.org')
|
335 | ]::some(2)
|
336 | ```
|
337 |
|
338 | #### promise::tap(onResolved, onRejected)
|
339 |
|
340 | > Like `.then()` but the original resolution/rejection is forwarded.
|
341 | >
|
342 | > Like `::lastly()`, if the callback rejects, it takes over the
|
343 | > original resolution/rejection.
|
344 |
|
345 | ```js
|
346 | import { tap } from 'promise-toolbox'
|
347 |
|
348 | // Contrary to .then(), using ::tap() does not change the resolution
|
349 | // value.
|
350 | const promise1 = Promise.resolve(42)::tap(value => {
|
351 | console.log(value)
|
352 | })
|
353 |
|
354 | // Like .then, the second param is used in case of rejection.
|
355 | const promise2 = Promise.reject(42)::tap(null, reason => {
|
356 | console.error(reason)
|
357 | })
|
358 | ```
|
359 |
|
360 | #### promise::timeout(ms, [cb])
|
361 |
|
362 | > Call a callback if the promise is still pending after `ms`
|
363 | > milliseconds. Its resolution/rejection is forwarded.
|
364 | >
|
365 | > If the callback is omitted, the returned promise is rejected with a
|
366 | > `Timeout` error.
|
367 |
|
368 | ```js
|
369 | import { timeout } from 'promise-toolbox'
|
370 |
|
371 | await doLongOperation()::timeout(100, () => {
|
372 | return doFallbackOperation()
|
373 | })
|
374 |
|
375 | await doLongOperation()::timeout(100)
|
376 | ```
|
377 |
|
378 | ## Development
|
379 |
|
380 | ### Installing dependencies
|
381 |
|
382 | ```
|
383 | > npm install
|
384 | ```
|
385 |
|
386 | ### Compilation
|
387 |
|
388 | The sources files are watched and automatically recompiled on changes.
|
389 |
|
390 | ```
|
391 | > npm run dev
|
392 | ```
|
393 |
|
394 | ### Tests
|
395 |
|
396 | ```
|
397 | > npm run test-dev
|
398 | ```
|
399 |
|
400 | ## Contributions
|
401 |
|
402 | Contributions are *very* welcomed, either on the documentation or on
|
403 | the code.
|
404 |
|
405 | You may:
|
406 |
|
407 | - report any [issue](https://github.com/JsCommunity/promise-toolbox/issues)
|
408 | you've encountered;
|
409 | - fork and create a pull request.
|
410 |
|
411 | ## License
|
412 |
|
413 | ISC © [Julien Fontanet](https://github.com/julien-f)
|