{"id":15498196,"url":"https://github.com/getify/caf","last_synced_at":"2025-05-14T19:02:50.883Z","repository":{"id":46330875,"uuid":"116604581","full_name":"getify/CAF","owner":"getify","description":"Cancelable Async Flows (CAF)","archived":false,"fork":false,"pushed_at":"2022-08-27T15:09:24.000Z","size":228,"stargazers_count":1346,"open_issues_count":2,"forks_count":48,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-04-06T08:08:36.670Z","etag":null,"topics":["async","cancelable","javascript","library","promises"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["getify"],"patreon":"getify","custom":["https://www.paypal.com/paypalme2/getify","https://www.blockchain.com/btc/address/32R5dVrqirdcbiyvUw85y7YbPFZTd7YpnH"]}},"created_at":"2018-01-07T22:36:50.000Z","updated_at":"2025-03-13T14:10:16.000Z","dependencies_parsed_at":"2022-08-12T12:50:43.787Z","dependency_job_id":null,"html_url":"https://github.com/getify/CAF","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getify%2FCAF","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getify%2FCAF/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getify%2FCAF/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getify%2FCAF/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getify","download_url":"https://codeload.github.com/getify/CAF/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248710401,"owners_count":21149185,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","cancelable","javascript","library","promises"],"created_at":"2024-10-02T08:42:20.042Z","updated_at":"2025-04-13T11:45:28.847Z","avatar_url":"https://github.com/getify.png","language":"JavaScript","readme":"# Cancelable Async Flows (CAF)\n\n[![Build Status](https://travis-ci.org/getify/CAF.svg?branch=master)](https://travis-ci.org/getify/CAF)\n[![npm Module](https://badge.fury.io/js/caf.svg)](https://www.npmjs.org/package/caf)\n[![Coverage Status](https://coveralls.io/repos/github/getify/caf/badge.svg?branch=master)](https://coveralls.io/github/getify/caf?branch=master)\n[![Modules](https://img.shields.io/badge/modules-ESM%2BUMD%2BCJS-a1356a)](https://nodejs.org/api/packages.html#dual-commonjses-module-packages)\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\n**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).\n\nAlso included is `CAG(..)`, for alternately wrapping `function*` generators to emulate ES2018 async-generators (`async function *`).\n\n## Environment Support\n\nThis library uses ES2018 features. If you need to support environments prior to ES2018, transpile it first (with Babel, etc).\n\n## At A Glance\n\n**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:\n\n```js\nvar token = new CAF.cancelToken();\n\n// wrap a generator to make it look like a normal async\n// function that when called, returns a promise.\nvar main = CAF( function *main(signal,url){\n    var resp = yield ajax( url );\n\n    // want to be able to cancel so we never get here?!?\n    console.log( resp );\n    return resp;\n} );\n\n// run the wrapped async-looking function, listen to its\n// returned promise\nmain( token.signal, \"http://some.tld/other\" )\n.then( onResponse, onCancelOrError );\n\n// only wait 5 seconds for the ajax request!\nsetTimeout( function onElapsed(){\n    token.abort( \"Request took too long!\" );\n}, 5000 );\n```\n\nCreate 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.\n\nThe 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:\n\n```js\nvar token = new CAF.cancelToken();\n\nvar one = CAF( function *one(signal,v){\n    return yield two( signal, v );\n} );\n\nvar two = CAF( function *two(signal,v){\n    return yield three( signal, v );\n} );\n\nvar three = CAF( function *three(signal,v){\n    return yield ajax( `http://some.tld/?v=${v}` );\n} );\n\none( token.signal, 42 );\n\n// only wait 5 seconds for the request!\nsetTimeout( function onElapsed(){\n    token.abort( \"Request took too long!\" );\n}, 5000 );\n```\n\nIn 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.\n\n**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 Cancellation Signal Handling](#manual-cancellation-signal-handling) below for addressing this limitation.\n\n### CAG: Cancelable Async ~~Flows~~ *Generators*\n\nES2018 added \"async generators\", which is a pairing of `async function` and `function*` -- so you can use `await` and `yield` in the same function, `await` for unwrapping a promise, and `yield` for pushing a value out. An async-generator (`async function * f(..) { .. }`), like regular iterators, is designed to be sequentially iterated, but using the \"async iteration\" protocol.\n\nFor example, in ES2018:\n\n```js\nasync function *stuff(urls) {\n    for (let url of urls) {\n        let resp = await fetch(url);  // await a promise\n        yield resp.json();  // yield a value (even a promise for a value)\n    }\n}\n\n// async-iteration loop\nfor await (let v of stuff(assetURLs)) {\n    console.log(v);\n}\n```\n\nIn the same way that `CAF(..)` emulates an `async..await` function with a `function*` generator, the `CAG(..)` utility emulates an async-generator with a normal `function*` generator. You can cancel an async-iteration early (even if it's currently waiting internally on a promise) with a cancellation token.\n\nYou can also synchronously force-close an async-iterator by calling the `return(..)` on the iterator. With native async-iterators, `return(..)` is not actually synchronous, but `CAG(..)` patches this to allow synchronous closing.\n\nInstead of `yield`ing a promise the way you do with `CAF(..)`, you use a provided `pwait(..)` function with `yield`, like `yield pwait(somePromise)`. This allows a `yield ..value..` expression for pushing out a value through the iterator, as opposed to `yield pwait(..value..)` to locally wait for the promise to resolve. To emulate a `yield await ..value..` expression (common in async-generators), you use two `yield`s together: `yield yield pwait(..value..)`.\n\nFor example:\n\n```js\n// NOTE: this is CAG(..), not to be confused with CAF(..)\nvar stuff = CAG(function *stuff({ signal, pwait },urls){\n    for (let url of urls) {\n        let resp = yield pwait(fetch(url,{ signal }));  // await a promise\n        yield resp.json();  // yield a value (even a promise for a value)\n    }\n});\n\nvar timeout = CAF.timeout(5000,\"That's enough results!\");\nvar it = stuff(timeout,assetURLs);\n\ncancelBtn.addEventListener(\"click\",() =\u003e it.return(\"Stop!\"),false);\n\n// async-iteration loop\nfor await (let v of it) {\n    console.log(v);\n}\n```\n\nIn this snippet, the `stuff(..)` async-iteration can either be canceled if the 5-second timeout expires before iteration is complete, or the click of the cancel button can force-close the iterator early. The difference between them is that token cancellation would result in an exception bubbling out (to the consuming loop), whereas calling `return(..)` will simply cleanly close the iterator (and halt the loop) with no exception.\n\n## Background/Motivation\n\nGenerally speaking, an `async function` and a `function*` generator (driven with a [generator-runner](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/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.\n\nHowever, 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.\n\nOne 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.\n\n`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.\n\n**CAF** provides a useful compromise: a `function*` generator that can be called like a normal `async function`, but which supports a cancellation token.\n\nThe `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.\n\nBy contrast, the `CAG(..)` utility wraps a `function*` generator as an ES2018 async-generator (`async function *`) that respects the native async-iteration protocol. Instead of `await`, you use `yield pwait(..)` in these emulated async-generators.\n\n## Overview\n\nIn 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:\n\n```js\nasync function one(v) {\n    await delay( 100 );\n    return v * 2;\n}\n\nvar two = CAF( function *two(signal,v){\n    yield delay( 100 );\n    return v * 2;\n} );\n```\n\nBoth `one(..)` and `two(..)` can be called directly with argument(s), and both return a promise for their completion:\n\n```js\none( 21 )\n.then( console.log, console.error );   // 42\n\nvar token = new CAF.cancelToken();\n\ntwo( token.signal, 21 )\n.then( console.log, console.error );   // 42\n```\n\nIf `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:\n\n```js\ntwo( token, 21 )\n.then( console.log, console.error );    // Took too long!\n\ntoken.abort( \"Took too long!\" );\n```\n\n### Delays \u0026 Timeouts\n\nOne of the most common use-cases for cancellation of an async task is because too much time passes and a timeout threshold is passed.\n\nAs 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\n\nConsider this code:\n\n```js\nfunction delay(ms) {\n    return new Promise( function c(res){\n        setTimeout( res, ms );\n    } );\n}\n\nvar token = new CAF.cancelToken();\n\nvar main = CAF( function *main(signal,ms){\n    yield delay( ms );\n    console.log( \"All done!\" );\n} );\n\nmain( token.signal, 100 );\n\n// only wait 5 seconds for the request!\ndelay( 5000 ).then( function onElapsed(){\n    token.abort( \"Request took too long!\" );\n} );\n```\n\nThe `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.\n\nOf 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.\n\nThere's two complications that make avoiding this downside tricky:\n\n1. 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.\n\n2. 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.\n\nInstead of inventing solutions to these problems, **CAF** provides two utilities for managing cancelable delays and timeout cancellations: `CAF.delay(..)` and `CAF.timeout(..)`.\n\n#### `CAF.delay(..)`\n\nWhat 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:\n\n```js\nvar discardTimeout = new CAF.cancelToken();\n\n// a promise that waits 5 seconds\nCAF.delay( discardTimeout.signal, 5000 )\n.then(\n    function onElapsed(msg){\n        // msg: \"delayed: 5000\"\n    },\n    function onInterrupted(reason){\n        // reason: \"delay (5000) interrupted\"\n    }\n);\n```\n\nAs 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:\n\n```js\ndiscardTimeout.abort();     // cancel the `CAF.delay()` timeout\n```\n\nThe 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\"`.\n\nPassing the cancellation token to `CAF.delay(..)` is optional; if omitted, `CAF.delay(..)` works just like a regular promisified `setTimeout(..)`:\n\n```\n// promise that waits 200 ms\nCAF.delay( 200 )\n.then( function onElapsed(){\n    console.log( \"Some time went by!\" );\n} );\n```\n\n#### `CAF.timeout(..)`\n\nWhile `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:\n\n```js\nvar timeoutToken = CAF.timeout( 5000, \"Took too long!\" );\n\nvar main = CAF( function *main(signal,ms){\n    yield CAF.delay( signal, ms );\n    console.log( \"All done!\" );\n} );\n\nmain( timeoutToken, 100 );   // NOTE: pass the whole token, not just the .signal !!\n```\n\n`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.\n\nNote 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!\n\nAlso 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.\n\n`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.\n\n### `finally { .. }`\n\n`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).\n\nCanceling 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.\n\n```js\nvar token = new CAF.cancelToken();\n\nvar main = CAF( function *main(signal,url){\n    try {\n        return yield ajax( url );\n    }\n    finally {\n        // perform some clean-up operations\n    }\n} );\n\nmain( token.signal, \"http://some.tld/other\" )\n.catch( console.log );     // 42 \u003c-- not \"Stopped!\"\n\ntoken.abort( \"Stopped!\" );\n```\n\nMoreover, a `return` of any non-`undefined` value in a pending `finally {..}` clause will override the promise rejection reason:\n\n```js\nvar token = new CAF.cancelToken();\n\nvar main = CAF( function *main(signal,url){\n    try {\n        return yield ajax( url );\n    }\n    finally {\n        return 42;\n    }\n} );\n\nmain( token.signal, \"http://some.tld/other\" )\n.catch( console.log );     // 42 \u003c-- not \"Stopped!\"\n\ntoken.abort( \"Stopped!\" );\n```\n\nWhatever 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.\n\n#### `signal.aborted` and `signal.reason`\n\nStandard [`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. Recently, `AbortSignal` was extended to include a `reason` property. Prior to that change,**CAF**was manually patching `signal` with a `reason` property, but now**CAF**respects the `reason` that's built-in to `AbortSignal` instances, if the environment supports it.\n\nTo set the `reason` for an abort-signal firing, pass a value to the [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)'s `abort(..)` call.\n\nBy 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:\n\n```js\nvar token = new CAF.cancelToken();\n\nvar main = CAF( function *main(signal,url){\n    try {\n        return yield ajax( url );\n    }\n    finally {\n        if (signal.aborted) {\n            console.log( `Cancellation reason: ${ signal.reason }` );\n            // perform cancellation-specific clean-up operations\n        }\n        else {\n            // perform non-cancellation clean-up operations\n        }\n    }\n} );\n\nmain( token.signal, \"http://some.tld/other\" );\n// Cancellation reason: Stopped!\n\ntoken.abort( \"Stopped!\" );\n```\n\n### Memory Cleanup With `discard()`\n\nA cancellation token from**CAF**includes a `discard()` method that can be called at any time to fully unset any internal state in the token to allow proper GC (garbage collection) of any attached resources.\n\nWhen you are sure you're fully done with a cancellation token, it's a good idea to call `discard()` on it, and then unset the variable holding that reference:\n\n```js\nvar token = new CAF.cancelToken();\n\n// later\ntoken.discard();\ntoken = null;\n```\n\nOnce a token has been `discard()`ed, no further calls to `abort(..)` will be effective -- they will silently be ignored.\n\n### `AbortController(..)`\n\n`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:\n\n```js\nvar token = new CAF.cancelToken();\n\nvar main = CAF(function *main(signal,url) {\n    var resp = yield fetch( url, { signal } );\n\n    console.log( resp );\n    return resp;\n});\n\nmain( token.signal, \"http://some.tld/other\" )\n.catch( console.log );   // \"Stopped!\"\n\ntoken.abort( \"Stopped!\" );\n```\n\n`CAF.cancelToken(..)` can optionally receive an already instantiated `AbortController`, though there's rarely a reason to do so:\n\n```js\nvar ac = new AbortController();\nvar token = new CAF.cancelToken(ac);\n```\n\nAlso, if you pass a raw `AbortController` instance into a **CAF**-wrapped function, it's automatically wrapped into a `CAF.cancelToken(..)` instance:\n\n```js\nvar main = CAF(function *main(signal,url) {\n    var resp = yield fetch( url, { signal } );\n\n    console.log( resp );\n    return resp;\n});\n\nvar ac = new AbortController();\nmain( ac, \"http://some.tld/other\" )\n.catch( () =\u003e console.log(\"Stopped!\") );   // \"Stopped!\"\n\nac.abort();\n```\n\n#### `AbortController()` Polyfill\n\nIf `AbortController` is not defined in the environment, use this [polyfill](https://github.com/mo/abortcontroller-polyfill) to define a compatible stand-in. The polyfill is included in the `dist/` directory.\n\nIf you load **CAF** in Node using its CJS format (with `require(..)`) and use the main package entry point (`require(\"caf\")`), the polyfill is automatically loaded (in the `global` namespace). If you don't use this entry point, but instead load something more directly, like `require(\"caf/core\")` or `require(\"caf/cag\")`, then you need to manually load the polyfill first:\n\n```js\nrequire(\"/path/to/caf/dist/abortcontroller-polyfill-only.js\");\n\nvar CAF = require(\"caf/core\");\nvar CAG = require(\"caf/cag\");\n```\n\nWhen using the ESM format of **CAF** in Node, the polyfill is *not* loaded automatically. Node 15/16+ includes `AbortController` natively, but in prior versions of Node (12-14) while using the ESM format, you need to manually `require(..)` the polyfill (before `import`ing **CAF**) like this:\n\n```js\nimport { createRequire } from \"module\";\nconst require = createRequire(import.meta.url);\nrequire(\"/path/to/caf/dist/abortcontroller-polyfill-only.js\");\n\nimport CAF from \"caf/core\";\n// ..\n```\n\nBe aware that if any environment needs this polyfill, utilities in that environment like `fetch(..)` won't *know* about `AbortController` so they won't recognize or respond to it. They won't break in its presence, just won't use it.\n\n### Manual Cancellation Signal Handling\n\nEven 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:\n\n```js\nvar token = new CAF.cancelToken();\n\nvar main = CAF( function *main(signal,url){\n    // listen to the signal's promise rejection directly\n    signal.pr.catch( reason =\u003e {\n        // reason == \"Stopped!\"\n    } );\n\n    var resp = yield ajax( url );\n\n    console.log( resp );\n    return resp;\n} );\n\nmain( token.signal, \"http://some.tld/other\" )\n.catch( console.log );   // \"Stopped!\"\n\ntoken.abort( \"Stopped!\" );\n```\n\n**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.\n\nEven if you aren't running a **CAF**-wrapped function, you can still respond to the cancellation `signal`'s promise manually to affect flow control:\n\n```js\nvar token = new CAF.cancelToken();\n\n// normal async function, not CAF-wrapped\nasync function main(signal,url) {\n    try {\n        var resp = await Promise.race( [\n            ajax( url ),\n            signal.pr       // listening to the cancellation\n        ] );\n\n        // this won't run if `signal.pr` rejects\n        console.log( resp );\n        return resp;\n    }\n    catch (err) {\n        // err == \"Stopped!\"\n    }\n}\n\nmain( token.signal, \"http://some.tld/other\" )\n.catch( console.log );   // \"Stopped!\"\n\ntoken.abort( \"Stopped!\" );\n```\n\n**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.\n\n### Signal Combinators\n\nYou 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:\n\n```js\nvar timeout = CAF.timeout(5000,\"Took too long!\");\nvar canceled = new CAF.cancelToken();\nvar exit = new AbortController();\n\nvar anySignal = CAF.signalRace([\n    timeout.signal,\n    canceled.signal,\n    exit.signal\n]);\n\nvar allSignals = CAF.signalAll([\n    timeout.signal,\n    canceled.signal,\n    exit.signal\n]);\n\nmain( anySignal, \"http://some.tld/other\" );\n\n// or\n\nmain( allSignals, \"http://some.tld/other\" );\n```\n\n`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.\n\n`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.\n\n**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.\n\n### Beware Of Token Reuse\n\nBeware 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.\n\nAs 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` -\u003e `B` -\u003e `C`). But reusing the same token **across two or more chains of calls** (`A` -\u003e `B` -\u003e `C` ***and*** `D` -\u003e `E` -\u003e `F`) is asking for trouble.\n\nImagine 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:\n\n```js\nvar one = CAF( function *one(signal){\n    signal.pr.catch( reason =\u003e {\n        console.log( `one: ${reason}` );\n    } );\n\n    return yield fetch( \"http://some.tld/\", {signal} );\n} );\n\nvar two = CAF( function *two(signal,v){\n    signal.pr.catch( reason =\u003e {\n        console.log( `two: ${reason}` );\n    } );\n\n    return yield fetch( `http://other.tld/?v=${v}`, {signal} );\n} );\n\nvar token = CAF.cancelToken();\n\none( token.signal )\n.then( function(v){\n    // only wait 3 seconds for this request\n    setTimeout( function(){\n        token.abort( \"Second response too slow.\" );\n    }, 3000 );\n\n    return two( token.signal, v );\n} )\n.then( console.log, console.error );\n\n// one: Second response too slow.   \u003c-- Oops!\n// two: Second response too slow.\n// Second response too slow.\n```\n\nWhen 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.\n\nThe 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.\n\nThe 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.\n\nThe 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, and make sure to call [`discard()`](#memory-cleanup-with-discard); thus, you won't accidentally reuse the token, and the JS engine can properly GC (garbage collect) it.\n\n### Cycling Tokens\n\nA common use case in managing async operations is when a currently pending operation needs to be canceled only because it's being replaced by a subsequent operation.\n\nFor example, imagine a button on a page that requests some remote data to display. If the user clicks the button again while a previous request is still pending, you can likely discard/cancel the previous request and start up a new fresh request in its place.\n\nIn these sorts of cases, you may find yourself \"cycling\" through cancellation tokens, where you store a reference to the current token, then when a new one is needed, the former token is aborted (to cancel all its chained operations) and replaced with the new token instance. This sort of logic is not too complex, but it does require keeping the token around across async operations, which unfortunately pollutes an outer scope.\n\nThis use case is common enough to warrant a standard helper shipped with this library to reduce the friction/impact of managing these cycles of tokens. **CAF** ships with `tokenCycle()` for this purpose:\n\n```js\n// create a token cycle\nvar getReqToken = CAF.tokenCycle();\n\nbtn.addEventListener(\"click\",function onClick(){\n    // get a new cancellation token, and\n    // cancel the previous token (if any)\n    //\n    // note: this function optionally takes a\n    //   reason for aborting the previous token\n    var cancelToken = getReqToken();\n\n    requestUpdatedData(cancelToken,\"my-data\");\n});\n```\n\nThe `tokenCycle()` function creates a separate instance of the token cycle manager, so you can create as many independent cycles as your app needs. It returns a function (named `getReqToken()` in the above snippet) which, when called, will produce a new token and cancel the previous token (if one is pending). This function also **optionally** takes a single argument to use as the *reason* passed in to abort the previous token.\n\nYou can of course keep these tokens around and use them for other cancellation controls. But in that situation you likely don't need `tokenCycle()`. This helper is designed for the lightweight use case where you wouldn't otherwise need to keep the token other than to make sure the previous operation is canceled before being replaced with the new operation.\n\n## CAG: Emulating Async Generators\n\nWhere `CAF(..)` emulates a promise-returning `async function` using a generator, `CAG(..)` is provided to emulate an async-iterator returning async-generator (`async function*`).\n\nAsync iteration is similar to streams (or primitive observables), where the values are consumed asynchronously (typically using an ES2018 `for await (..)` loop):\n\n```js\nfor await (let v of someAsyncGenerator()) {\n    // ..\n}\n\n// or:\nvar it = someAsyncGenerator();\nfor await (let v of it) {\n    // ..\n}\n```\n\nFor all the same reasons that `async function`s being non-cancelable is troublesome, async-generators are similarly susceptible. An async-generator can be \"stuck\" `await`ing internally on a promise, and the outer consuming code cannot do anything to force it to stop.\n\nThat's why `CAG(..)` is useful:\n\n```js\n// instead of:\nasync function *stuff(urls) {\n    for (let url of urls) {\n        let resp = await fetch(url);  // await a promise\n        yield resp.json();  // yield a value (even a promise for a value)\n    }\n}\n\n// do this:\nvar stuff = CAG(function *stuff({ signal, pwait },urls){\n    for (let url of urls) {\n        let resp = yield pwait(fetch(url,{ signal }));  // await a promise\n        yield resp.json();  // yield a value (even a promise for a value)\n    }\n});\n```\n\nLike `CAF(..)`, functions wrapped by `CAG(..)` expect to receive a special value in their first parameter position. Here, the object is destructured to reveal it contains both the cancellation-token `signal` (as with `CAF(..)`) and the `pwait(..)` function, which enables emulating local `await ..promise..` expressions as `yield pwait(..promise..)`.\n\nThe return from a `CAG(..)` wrapped function is an async-iterator (exactly as if a real native async-generator had been invoked). As with `CAF(..)` values, the first argument passed should always be the mandatory cancellation token (or its signal):\n\n```js\nvar stuff = CAG(function *stuff(..){ .. });\n\nvar timeout = CAF.timeout(5000,\"Took too long.\");\nvar it = stuff(timeout);\n```\n\nThe returned async-iterator (`it` above) can be iterated manually with `it.next(..)` calls -- each returns a promise for an iterator-result -- or more preferably with an ES2018 `for await (..)` loop:\n\n```js\nvar timeout = CAF.timeout(5000,\"Took too long.\");\nvar it = stuff(timeout);\n\nvar { value, done } = await it.next();\n// ..do that repeatedly..\n\n// or preferably:\nfor await (let value of it) {\n    // ..\n}\n```\n\nIn addition to being able to `abort(..)` the cancellation token passed into a `CAG(..)`-wrapped generator, async-iterators also can be closed forcibly by calling their `return(..)` method.\n\n```js\nvar timeout = CAF.timeout(5000,\"Took too long.\");\nvar it = stuff(timeout);\n\n// later (e.g. in a timer or event handler):\nit.return(\"all done\");\n// Promise\u003c{ value: \"all done\", done: true }\u003e\n```\n\nTypically, the `return(..)` call on an async-iterator (from an async-generator) will have \"wait\" for the attached async-generator to be \"ready\" to be closed -- in case an `await promise` expression is currently pending. This means you cannot actually synchronously force-close them. But since `CAG(..)` emulates async-generators with regular sync-generators, this nuance is \"fixed\". For consistency, `return(..)` still returns a Promise, but it's an already-resolved promise with the associated iterator-result.\n\n`CAG(..)`-wrapped functions also follow these behaviors of `CAF(..)`-wrapped functions:\n\n* Aborting the cancellation token results in an exception (which can be trapped by `try..catch`) propagating out from the most recent `for await (..)` (or `it.next(..)`) consumption point.\n\n* The `reason` provided when aborting a cancellation token is (by default) set as the exception that propagates out. This can be overriden by a `return ..` statement in a `finally` clause of the wrapped generator function.\n\n#### Event Streams\n\nOne of the most common use cases for async iterators (aka, streams) is to subscribe to an event source (DOM element events, Node.js event emitters, etc) and iterate the received events.\n\n**CAG** provides two helpers for event stream subscription: `onEvent(..)` and `onceEvent(..)`. As the name implies, `onEvent(..)` listens for all events, whereas `onceEvent(..)` will listen only for a single event to fire (and then close the stream and unsubscribe from the event emitter).\n\n`onEvent(..)` returns an ES2018 async iterator, but `onceEvent(..)` returns a promise that resolves (with the event value, if any) when the event fires.\n\n```js\nvar cancel = new CAF.cancelToken();\n\nvar DOMReady = CAG.onceEvent(cancel,document,\"DOMContentLoaded\",/*evtOpts=*/false);\nvar clicks = CAG.onEvent(cancel,myBtn,\"click\",/*evtOpts=*/false);\n\n// wait for this one-time event to fire\nawait DOMReady;\n\nfor await (let click of clicks) {\n    console.log(\"Button clicked!\");\n}\n```\n\n`onEvent(..)` event subscriptions are lazy, meaning that they don't actually attach to the emitter element until the first attempt to consume an event (via `for await..of` or a manual `next(..)` call on the async iterator). So, in the above snippet, the `clicks` event stream is not yet subscribed to any click events that happen until the `for await..of` loop starts (e.g., while waiting for the prior `DOMReady` event to fire).\n\nHowever, there may be cases where you want to force the event subscription to start early even before consuming its events. Use `start()` to do so:\n\n```js\nvar cancel = new CAF.cancelToken();\n\nvar clicks = CAG.onEvent(cancel,myBtn,\"click\",/*evtOpts=*/false);\n\n// force eager listening to events\nclicks.start();\n\n// .. consume the stream later ..\n```\n\nEvent streams internally buffer received events that haven't yet been consumed. This buffer grows unbounded, so responsible memory management implies always consuming events from a stream that is subscribed and actively receving events.\n\nOnce an event stream is closed (e.g., token cancellation, breaking out of a `for await..of` loop, manually calling `return(..)` on the async iterator), the underlying event is unsubscribed.\n\n## npm Package\n\n[![npm Module](https://badge.fury.io/js/caf.svg)](https://www.npmjs.org/package/caf)\n[![Modules](https://img.shields.io/badge/modules-ESM%2BUMD%2BCJS-a1356a)](https://nodejs.org/api/packages.html#dual-commonjses-module-packages)\n\nTo install this package from `npm`:\n\n```\nnpm install caf\n```\n\n**IMPORTANT:** The **CAF** library relies on [`AbortController`](#abortcontroller) being [present in the JS environment](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). If the environment does not already define `AbortController` natively, **CAF** needs a [polyfill for `AbortController`](#abortcontroller-polyfill). In some cases, the polyfill is automatically loaded, and in other cases it must be manually required/imported. See the linked section for more details.\n\nAs of version 12.0.0, the package is available as an ES Module (in addition to CJS/UMD), and can be imported as so:\n\n```js\n// named imports\nimport { CAF, CAG } from \"caf\";\n\n// or, default imports:\nimport CAF from \"caf/core\";\nimport CAG from \"caf/cag\";\n```\n\n**Note:** Starting in version 15.0.0, the (somewhat confusing) ESM specifier `\"caf/caf\"` (which imports **only** `CAF` as a default-import) has been deprecated and will eventually be removed. Use `\"caf/core\"` to default-import only the `CAF` module, or use just `\"caf\"` for named imports (`{ CAF, CAG }`).\n\n**Also Note:** Starting in version 11.x, **CAF** was also available in ESM format, but required an ESM import specifier segment `/esm` in **CAF** `import` paths. As of version 15.0.0, this has been removed, in favor of unified import specifier paths via [Node Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports). For ESM `import` statements, always use the specifier style `\"caf\"` or `\"caf/cag\"`, instead of `\"caf/esm\"` and `\"caf/esm/cag\"`, respectively.\n\nTo use **CAF** in Node via CJS format (with `require(..)`):\n\n```js\nvar CAF = require(\"caf\");\nvar CAG = require(\"caf/cag\");\n```\n\n## Builds\n\n[![Build Status](https://travis-ci.org/getify/CAF.svg?branch=master)](https://travis-ci.org/getify/CAF)\n[![npm Module](https://badge.fury.io/js/caf.svg)](https://www.npmjs.org/package/caf)\n[![Modules](https://img.shields.io/badge/modules-ESM%2BUMD%2BCJS-a1356a)](https://nodejs.org/api/packages.html#dual-commonjses-module-packages)\n\nThe distribution files come pre-built with the npm package distribution, so you shouldn't need to rebuild it under normal circumstances.\n\nHowever, if you download this repository via Git:\n\n1. The included build utility (`scripts/build-core.js`) builds (and minifies) the `dist/*` files.\n\n2. To install the build and test dependencies, run `npm install` from the project root directory.\n\n3. To manually run the build utility with npm:\n\n    ```\n    npm run build\n    ```\n\n4. To run the build utility directly without npm:\n\n    ```\n    node scripts/build-core.js\n    ```\n\n## Tests\n\nA test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using the files in `src/`.\n\n1. The tests are run with QUnit.\n\n2. You can run the tests in a browser by opening up `tests/index.html`.\n\n3. To run the test utility:\n\n    ```\n    npm test\n    ```\n\n    Other npm test scripts:\n\n    * `npm run test:package` will run the test suite as if the package had just been installed via npm. This ensures `package.json`:`main` and `exports` entry points are properly configured.\n\n    * `npm run test:umd` will run the test suite against the `dist/umd/*` files instead of the `src/*` files.\n\n    * `npm run test:esm` will run the test suite against the `dist/esm/*` files instead of the `src/*` files.\n\n    * `npm run test:all` will run all four modes of the test suite.\n\n### Test Coverage\n\n[![Coverage Status](https://coveralls.io/repos/github/getify/caf/badge.svg?branch=master)](https://coveralls.io/github/getify/caf?branch=master)\n\nIf you have [NYC (Istanbul)](https://github.com/istanbuljs/nyc) already installed on your system (requires v14.1+), you can use it to check the test coverage:\n\n```\nnpm run coverage\n```\n\nThen open up `coverage/lcov-report/index.html` in a browser to view the report.\n\n**Note:** The npm script `coverage:report` is only intended for use by project maintainers. It sends coverage reports to [Coveralls](https://coveralls.io/).\n\n## License\n\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\nAll code and documentation are (c) 2022 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt).\n","funding_links":["https://github.com/sponsors/getify","https://patreon.com/getify","https://www.paypal.com/paypalme2/getify","https://www.blockchain.com/btc/address/32R5dVrqirdcbiyvUw85y7YbPFZTd7YpnH"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetify%2Fcaf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetify%2Fcaf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetify%2Fcaf/lists"}