1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595# Cancelable Async Flows (CAF)
[](https://travis-ci.org/getify/CAF)
[](https://www.npmjs.org/package/caf)
[](https://david-dm.org/getify/caf)
[](https://david-dm.org/getify/caf?type=dev)
[](https://coveralls.io/github/getify/caf?branch=master)
**CAF** (/ˈkahf/) is a wrapper for `function*` generators that treats them like `async function`s, but with support for external cancellation via tokens. In this way, you can express flows of synchronous-looking asynchronous logic that are still cancelable (**C**ancelable **A**sync **F**lows).
## Environment Support
This utility uses ES6 (aka ES2015) features. If you need to support environments prior to ES6, transpile it first (with Babel, etc).
## At A Glance
**CAF** (**C**ancelable **A**sync **F**lows) wraps a `function*` generator so it looks and behaves like an `async function`, but that can be externally canceled using a cancellation token:
```js
var token = new CAF.cancelToken();
// wrap a generator to make it look like a normal async
// function that when called, returns a promise.
var main = CAF( function *main(signal,url){
var resp = yield ajax( url );
// want to be able to cancel so we never get here?!?
console.log( resp );
return resp;
} );
// run the wrapped async-looking function, listen to its
// returned promise
main( token.signal, "http://some.tld/other" )
.then( onResponse, onCancelOrError );
// only wait 5 seconds for the ajax request!
setTimeout( function onElapsed(){
token.abort( "Request took too long!" );
}, 5000 );
```
Create a cancellation token (via `new CAF.cancelToken()`) to pass into your wrapped `function*` generator, and then if you cancel the token, the `function*` generator will abort itself immediately, even if it's presently waiting on a promise to resolve.
The generator receives the cancellation token's `signal`, so from inside it you can call another `function*` generator via **CAF** and pass along that shared `signal`. In this way, a single cancellation signal can cascade across and cancel all the **CAF**-wrapped functions in a chain of execution:
```js
var token = new CAF.cancelToken();
var one = CAF( function *one(signal,v){
return yield two( signal, v );
} );
var two = CAF( function *two(signal,v){
return yield three( signal, v );
} );
var three = CAF( function *three(signal,v){
return yield ajax( `http://some.tld/?v=${v}` );
} );
one( token.signal, 42 );
// only wait 5 seconds for the request!
setTimeout( function onElapsed(){
token.abort( "Request took too long!" );
}, 5000 );
```
In this snippet, `one(..)` calls and waits on `two(..)`, `two(..)` calls and waits on `three(..)`, and `three(..)` calls and waits on `ajax(..)`. Because the same cancellation token is used for the 3 generators, if `token.abort()` is executed while they're all still paused, they will all immediately abort.
**Note:** The cancellation token has no effect on the actual `ajax(..)` call itself here, since that utility ostensibly doesn't provide cancellation capability; the Ajax request itself would still run to its completion (or error or whatever). We've only canceled the `one(..)`, `two(..)`, and `three(..)` functions that were waiting to process its response. See [`AbortController(..)`](#abortcontroller) and [Manual Cancelation Signal Handling](#manual-cancellation-signal-handling) below for addressing this limitation.
## Background/Motivation
Generally speaking, an `async function` and a `function*` generator (driven with a [generator-runner](https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch4.md#promise-aware-generator-runner)) look very similar. For that reason, most people just prefer the `async function` form since it's a little nicer syntax and doesn't require a library for the runner.
However, there are limitations to `async function`s that come from having the syntax and engine make implicit assumptions that otherwise would have been handled by a `function*` generator runner.
One unfortunate limitation is that an `async function` cannot be externally canceled once it starts running. If you want to be able to cancel it, you have to intrusively modify its definition to have it consult an external value source -- like a boolean or promise -- at each line that you care about being a potential cancellation point. This is ugly and error-prone.
`function*` generators by contrast can be aborted at any time, using the iterator's `return(..)` method and/or by just not resuming the generator iterator instance with `next()`. But the downside of using `function*` generators is either needing a runner utility or the repetitive boilerplate of handling the iterator manually.
**CAF** provides a useful compromise: a `function*` generator that can be called like a normal `async function`, but which supports a cancellation token.
The `CAF(..)` utility wraps a `function*` generator with a normal promise-returing function, just as if it was an `async function`. Other than minor syntactic aesthetics, the major observable difference is that a **CAF**-wrapped function must be provided a cancellation token's `signal` as its first argument, with any other arguments passed subsequent, as desired.
## Overview
In the following snippet, the two functions are essentially equivalent; `one(..)` is an actual `async function`, whereas `two(..)` is a wrapper around a generator, but will behave like an async function in that it also returns a promise:
```js
async function one(v) {
await delay( 100 );
return v * 2;
}
var two = CAF( function *two(signal,v){
yield delay( 100 );
return v * 2;
} );
```
Both `one(..)` and `two(..)` can be called directly with argument(s), and both return a promise for their completion:
```js
one( 21 )
.then( console.log, console.error ); // 42
var token = new CAF.cancelToken();
two( token.signal, 21 )
.then( console.log, console.error ); // 42
```
If `token.abort(..)` is executed while `two(..)` is still running, the `signal`'s promise will be rejected. If you pass a cancellation reason (any value, but typically a string) to `token.abort(..)`, that will be the promise rejection reason:
```js
two( token, 21 )
.then( console.log, console.error ); // Took too long!
token.abort( "Took too long!" );
```
### Delays & Timeouts
One of the most common use-cases for cancellation of an async task is because too much time passes and a timeout threshold is passed.
As shown earlier, you can implement that sort of logic with a `cancelToken()` instance and a manual call to the environment's `setTimeout(..)`. However, there are some subtle but important downsides to doing this kind of thing manually. These downsides are harder to spot in the browser, but are more obvious in Node.js
Consider this code:
```js
function delay(ms) {
return new Promise( function c(res){
setTimeout( res, ms );
} );
}
var token = new CAF.cancelToken();
var main = CAF( function *main(signal,ms){
yield delay( ms );
console.log( "All done!" );
} );
main( token.signal, 100 );
// only wait 5 seconds for the request!
delay( 5000 ).then( function onElapsed(){
token.abort( "Request took too long!" );
} );
```
The `main(..)` function delays for `100`ms and then completes. But there's no logic that clears the timeout set from `delay( 5000 )`, so it will continue to hold pending until that amount of time expires.
Of course, the `token.abort(..)` call at that point is moot, and is thus silently ignored. But the problem is the timer still running, which keeps a Node.js process alive even if the rest of the program has completed. The symptoms of this would be running a Node.js program from the command line and observing it "hang" for a bit at the end instead of exiting right away. Try the above code to see this in action.
There's two complications that make avoiding this downside tricky:
1. The `delay(..)` helper shown, which is a promisified version of `setTimeout(..)`, is basically what you can produce by using [Node.js's `util.promisify(..)`](https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original) against `setTimeout(..)`. However, that timer itself is not cancelable. You can't access the timer handle (return value from `setTimeout(..)`) to call `clearTimeout(..)` on it. So, you can't stop the timer early even if you wanted to.
2. If instead you set up your own timer externally, you need to keep track of the timer's handle so you can call `clearTimeout(..)` if the async task completes successfully before the timeout expires. This is manual and error-prone, as it's far too easy to forget.
Instead of inventing solutions to these problems, **CAF** provides two utilities for managing cancelable delays and timeout cancellations: `CAF.delay(..)` and `CAF.timeout(..)`.
#### `CAF.delay(..)`
What we need is a promisified `setTimeout(..)`, like `delay(..)` we saw in the previous section, but that can still be canceled. `CAF.delay(..)` provides us such functionality:
```js
var discardTimeout = new CAF.cancelToken();
// a promise that waits 5 seconds
CAF.delay( discardTimeout.signal, 5000 )
.then(
function onElapsed(msg){
// msg: "delayed: 5000"
},
function onInterrupted(reason){
// reason: "delay (5000) interrupted"
}
);
```
As you can see, `CAF.delay(..)` receives a cancellation token signal to cancel the timeout early when needed. If you need to cancel the timeout early, abort the cancellation token:
```js
discardTimeout.abort(); // cancel the `CAF.delay()` timeout
```
The promise returned from `CAF.delay(..)` is fulfilled if the full time amount elapses, with a message such as `"delayed: 5000"`. But if the timeout is aborted via the cancellation token, the promise is rejected with a reason like `"delay (5000) interrupted"`.
Passing the cancellation token to `CAF.delay(..)` is optional; if omitted, `CAF.delay(..)` works just like a regular promisified `setTimeout(..)`:
```
// promise that waits 200 ms
CAF.delay( 200 )
.then( function onElapsed(){
console.log( "Some time went by!" );
} );
```
#### `CAF.timeout(..)`
While `CAF.delay(..)` provides a cancelable timeout promise, it's still overly manual to connect the dots between a **CAF**-wrapped function and the timeout-abort process. **CAF** provides `CAF.timeout(..)` to streamline this common use-case:
```js
var timeoutToken = CAF.timeout( 5000, "Took too long!" );
var main = CAF( function *main(signal,ms){
yield CAF.delay( signal, ms );
console.log( "All done!" );
} );
main( timeoutToken, 100 ); // NOTE: pass the whole token, not just the .signal !!
```
`CAF.timeout(..)` creates an instance of `cancellationToken(..)` that's set to `abort()` after the specified amount of time, optionally using the cancellation reason you provide.
Note that you should pass the full `timeoutToken` token to the **CAF**-wrapped function (`main(..)`), instead of just passing `timeoutToken.signal`. By doing so, **CAF** wires the token and the **CAF**-wrapped function together, so that each one stops the other, whichever one happens first. No more hanging timeouts!
Also note that `main(..)` still receives just the `signal` as its first argument, which is suitable to pass along to other cancelable async functions, such as `CAF.delay(..)` as shown.
`timeoutToken` is a regular cancellation token as created by `CAF.cancelToken()`. As such, you can call `abort(..)` on it directly, if necessary. You can also access `timeoutToken.signal` to access its signal, and `timeoutToken.signal.pr` to access the promise that's rejected when the signal is aborted.
### `finally { .. }`
`finally` clauses are often attached to a `try { .. }` block wrapped around the entire body of a function, even if there's no `catch` clause defined. The most common use of this pattern is to define some clean-up operations to be performed after the function is finished, whether that was from normal completion or an early termination (such as uncaught exceptions, or cancellation).
Canceling a **CAF**-wrapped `function*` generator that is paused causes it to abort right away, but if there's a pending `finally {..}` clause, it will always still have a chance to run.
```js
var token = new CAF.cancelToken();
var main = CAF( function *main(signal,url){
try {
return yield ajax( url );
}
finally {
// perform some clean-up operations
}
} );
main( token.signal, "http://some.tld/other" )
.catch( console.log ); // 42 <-- not "Stopped!"
token.abort( "Stopped!" );
```
Moreover, a `return` of any non-`undefined` value in a pending `finally {..}` clause will override the promise rejection reason:
```js
var token = new CAF.cancelToken();
var main = CAF( function *main(signal,url){
try {
return yield ajax( url );
}
finally {
return 42;
}
} );
main( token.signal, "http://some.tld/other" )
.catch( console.log ); // 42 <-- not "Stopped!"
token.abort( "Stopped!" );
```
Whatever value is passed to `abort(..)`, if any, is normally set as the overall promise rejection reason. But in this case, `return 42` overrides the `"Stopped!"` rejection reason.
#### `signal.aborted` and `signal.reason`
Standard [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instances have an `aborted` boolean property that's set to `true` once the signal is aborted. Once aborted, CAF also extends signals to also include a `reason` property with the value (if any) passed to the [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)'s `abort(..)` call.
**Note:** Passing a `reason` to an `abort(..)` call on a cancellation token is CAF-specific; calling `abort()` on a direct `AbortController` instance will ignore any passed-in value, though all the rest of the CAF cancellation mechanism still works. See the following [`AbortController`](#abortcontroller) section for more information.
By checking the `signal.aborted` flag in a `finally` clause, you can determine whether the function was canceled, and then additionally access the `signal.reason` to determine more specific context information about why the cancellation occurred. This allows you to perform different clean-up operations depending on cancellation or normal completion:
```js
var token = new CAF.cancelToken();
var main = CAF( function *main(signal,url){
try {
return yield ajax( url );
}
finally {
if (signal.aborted) {
console.log( `Cancellation reason: ${ signal.reason }` );
// perform cancellation-specific clean-up operations
}
else {
// perform non-cancellation clean-up operations
}
}
} );
main( token.signal, "http://some.tld/other" );
// Cancellation reason: Stopped!
token.abort( "Stopped!" );
```
### `AbortController(..)`
`CAF.cancelToken(..)` instantiates [`AbortController`, the DOM standard](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling/aborting operations like `fetch(..)` calls. As such, a **CAF** cancellation token's `signal` can be passed directly to a DOM method like `fetch(..)` to control its cancellation:
```js
var token = new CAF.cancelToken();
var main = CAF(function *main(signal,url) {
var resp = yield fetch( url, { signal } );
console.log( resp );
return resp;
});
main( token.signal, "http://some.tld/other" )
.catch( console.log ); // "Stopped!"
token.abort( "Stopped!" );
```
`CAF.cancelToken(..)` can optionally receive an already instantiated `AbortController`, though there's rarely a reason to do so:
```js
var ac = new AbortController();
var token = new CAF.cancelToken(ac);
```
Also, if you pass a raw `AbortController` instance into a **CAF**-wrapped function, it's automatically wrapped into a `CAF.cancelToken(..)` instance:
```js
var main = CAF(function *main(signal,url) {
var resp = yield fetch( url, { signal } );
console.log( resp );
return resp;
});
var ac = new AbortController();
main( ac, "http://some.tld/other" )
.catch( () => console.log("Stopped!") ); // "Stopped!"
token.abort();
```
#### `AbortController()` Polyfill
If `AbortController` is not defined in the environment, use this [polyfill](https://github.com/mo/abortcontroller-polyfill) to define a compatible stand-in. The polyfill can be found in the `dist/` directory alongside `caf.js`.
**Note:** The polyfill is automatically loaded (in the `global` namespace) when **CAF** is used is Node.
Just be aware that if an environment needs the polyfill, `fetch(..)` and other such APIs won't know about `AbortController` so they won't recognize or respond to it. They won't break in its presence; they'll just ignore it.
### Manual Cancelation Signal Handling
Even if you aren't calling a cancellation signal-aware utility (like `fetch(..)`), you can still manually respond to the cancellation `signal` via its attached promise:
```js
var token = new CAF.cancelToken();
var main = CAF( function *main(signal,url){
// listen to the signal's promise rejection directly
signal.pr.catch( reason => {
// reason == "Stopped!"
} );
var resp = yield ajax( url );
console.log( resp );
return resp;
} );
main( token.signal, "http://some.tld/other" )
.catch( console.log ); // "Stopped!"
token.abort( "Stopped!" );
```
**Note:** The `catch(..)` handler inside of `main(..)` will still run, even though `main(..)` itself will be aborted at its waiting `yield` statement. If there was a way to manually cancel the `ajax(..)` call, that code should be placed in the `catch(..)` handler.
Even if you aren't running a **CAF**-wrapped function, you can still respond to the cancellation `signal`'s promise manually to affect flow control:
```js
var token = new CAF.cancelToken();
// normal async function, not CAF-wrapped
async function main(signal,url) {
try {
var resp = await Promise.race( [
ajax( url ),
signal.pr // listening to the cancellation
] );
// this won't run if `signal.pr` rejects
console.log( resp );
return resp;
}
catch (err) {
// err == "Stopped!"
}
}
main( token.signal, "http://some.tld/other" )
.catch( console.log ); // "Stopped!"
token.abort( "Stopped!" );
```
**Note:** As discussed earlier, the `ajax(..)` call itself is not cancellation-aware, and is thus not being canceled here. But we *are* ending our waiting on the `ajax(..)` call. When `signal.pr` wins the `Promise.race(..)` race and creates an exception from its promise rejection, flow control jumps straight to the `catch (err) { .. }` clause.
### Signal Combinators
You may want to combine two or more signals, similar to how you combine promises with `Promise.race(..)` and `Promise.all(..)`. **CAF** provides two corresponding helpers for this purpose:
```js
var timeout = CAF.timeout(5000,"Took too long!");
var canceled = new CAF.cancelToken();
var exit = new AbortController();
var anySignal = CAF.signalRace([
timeout.signal,
canceled.signal,
exit.signal
]);
var allSignals = CAF.signalAll([
timeout.signal,
canceled.signal,
exit.signal
]);
main( anySignal, "http://some.tld/other" );
// or
main( allSignals, "http://some.tld/other" );
```
`CAF.signalRace(..)` expects an array of one or more signals, and returns a new signal (`anySignal`) that will fire as soon as any of the constituent signals have fired.
`CAF.signalAll(..)` expects an array of one or more signals, and returns a new signal (`allSignals`) that will fire only once all of the constituent signals have fired.
**Warning:** This pattern (combining signals) has a potential downside. **CAF** typically cleans up timer-based cancel tokens to make sure resources aren't being wasted and programs aren't hanging with open timer handles. But in this pattern, `signalRace(..)` / `signalAll(..)` only receive reference(s) to the signal(s), not the cancel tokens themselves, so it cannot do the manual cleanup. In the above example, you should manually clean up the 5000ms timer by calling `timeout.abort()` if the operation finishes before that timeout has fired the cancellation.
### Beware Of Token Reuse
Beware of creating a single cancellation token that is reused for separate chains of function calls. Unexpected results are likely, and they can be extremely difficult to debug.
As illustrated earlier, it's totally OK and intended that a single cancellation token `signal` be shared across all the functions in **one** chain of calls (`A` -> `B` -> `C`). But reusing the same token **across two or more chains of calls** (`A` -> `B` -> `C` ***and*** `D` -> `E` -> `F`) is asking for trouble.
Imagine a scenario where you make two separate `fetch(..)` calls, one after the other, and the second one runs too long so you cancel it via a timeout:
```js
var one = CAF( function *one(signal){
signal.pr.catch( reason => {
console.log( `one: ${reason}` );
} );
return yield fetch( "http://some.tld/", {signal} );
} );
var two = CAF( function *two(signal,v){
signal.pr.catch( reason => {
console.log( `two: ${reason}` );
} );
return yield fetch( `http://other.tld/?v=${v}`, {signal} );
} );
var token = CAF.cancelToken();
one( token.signal )
.then( function(v){
// only wait 3 seconds for this request
setTimeout( function(){
token.abort( "Second response too slow." );
}, 3000 );
return two( token.signal, v );
} )
.then( console.log, console.error );
// one: Second response too slow. <-- Oops!
// two: Second response too slow.
// Second response too slow.
```
When you call `token.abort(..)` to cancel the second `fetch(..)` call in `two(..)`, the `signal.pr.catch(..)` handler in `one(..)` still gets called, even though `one(..)` is already finished. That's why `"one: Second response too slow."` prints unexpectedly.
The underlying gotcha is that a cancellation token's `signal` has a single `pr` promise associated with it, and there's no way to reset a promise or "unregister" `then(..)` / `catch(..)` handlers attached to it once you don't need them anymore. So if you reuse the token, you're reusing the `pr` promise, and all registered promise handlers will be fired, even old ones you likely don't intend.
The above snippet illustrates this problem with `signal.pr.catch(..)`, but any of the other ways of listening to a promise -- such as `yield` / `await`, `Promise.all(..)` / `Promise.race(..)`, etc -- are all susceptible to the unexpected behavior.
The safe and proper approach is to always create a new cancellation token for each chain of **CAF**-wrapped function calls. For good measure, always unset any references to a token once it's no longer needed; thus, you won't accidentally reuse it, and the JS engine can properly garbage collect it.
## npm Package
Prior to version 4.0.0, the package name was "async-caf", but starting with version 4.0.0, the name has been simplified to "caf". So, to install this package from `npm`:
```
npm install caf
```
And to require it in a node script:
```js
var CAF = require("caf");
```
## Builds
[](https://travis-ci.org/getify/CAF)
[](https://www.npmjs.org/package/caf)
The distribution library file (`dist/caf.js`) comes pre-built with the npm package distribution, so you shouldn't need to rebuild it under normal circumstances.
However, if you download this repository via Git:
1. The included build utility (`scripts/build-core.js`) builds (and minifies) `dist/caf.js` from source. **The build utility expects Node.js version 6+.**
2. To install the build and test dependencies, run `npm install` from the project root directory.
- **Note:** This `npm install` has the effect of running the build for you, so no further action should be needed on your part.
4. To manually run the build utility with npm:
```
npm run build
```
5. To run the build utility directly without npm:
```
node scripts/build-core.js
```
## Tests
A comprehensive test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using `src/caf.src.js`.
1. You can run the tests in a browser by opening up `tests/index.html` (**requires ES6+ browser environment**).
2. The included Node.js test utility (`scripts/node-tests.js`) runs the test suite. **This test utility expects Node.js version 6+.**
3. Ensure the test dependencies are installed by running `npm install` from the project root directory.
- **Note:** Starting with npm v5, the test utility is **not** run automatically during this `npm install`. With npm v4, the test utility automatically runs at this point.
4. To run the test utility with npm:
```
npm test
```
Other npm test scripts:
* `npm run test:dist` will run the test suite against `dist/caf.js` instead of the default of `src/caf.src.js`.
* `npm run test:package` will run the test suite as if the package had just been installed via npm. This ensures `package.json`:`main` properly references `dist/caf.js` for inclusion.
* `npm run test:all` will run all three modes of the test suite.
5. To run the test utility directly without npm:
```
node scripts/node-tests.js
```
### Test Coverage
[](https://coveralls.io/github/getify/caf?branch=master)
If you have [Istanbul](https://github.com/gotwarlost/istanbul) already installed on your system (requires v1.0+), you can use it to check the test coverage:
```
npm run coverage
```
Then open up `coverage/lcov-report/index.html` in a browser to view the report.
To run Istanbul directly without npm:
```
istanbul cover scripts/node-tests.js
```
**Note:** The npm script `coverage:report` is only intended for use by project maintainers. It sends coverage reports to [Coveralls](https://coveralls.io/).
## License
All code and documentation are (c) 2019 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt).