{"id":21531937,"url":"https://github.com/matrixai/js-async-cancellable","last_synced_at":"2025-08-09T16:22:59.153Z","repository":{"id":57867377,"uuid":"527128174","full_name":"MatrixAI/js-async-cancellable","owner":"MatrixAI","description":"Asynchronous Cancellation (Promises) for JavaScript/TypeScript","archived":false,"fork":false,"pushed_at":"2023-08-14T09:21:14.000Z","size":874,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"staging","last_synced_at":"2024-04-28T07:21:07.071Z","etag":null,"topics":["cancellation","promise"],"latest_commit_sha":null,"homepage":"https://polykey.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MatrixAI.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-21T06:47:27.000Z","updated_at":"2023-06-07T01:43:32.000Z","dependencies_parsed_at":"2024-11-24T02:19:52.140Z","dependency_job_id":"27c2322e-5afb-471f-b8b4-55794d0041a7","html_url":"https://github.com/MatrixAI/js-async-cancellable","commit_stats":{"total_commits":19,"total_committers":3,"mean_commits":6.333333333333333,"dds":0.3157894736842105,"last_synced_commit":"a8dfff98ca762da18b27397d2025b187e276c40b"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":"MatrixAI/TypeScript-Demo-Lib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatrixAI%2Fjs-async-cancellable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatrixAI%2Fjs-async-cancellable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatrixAI%2Fjs-async-cancellable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatrixAI%2Fjs-async-cancellable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MatrixAI","download_url":"https://codeload.github.com/MatrixAI/js-async-cancellable/tar.gz/refs/heads/staging","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244096079,"owners_count":20397333,"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":["cancellation","promise"],"created_at":"2024-11-24T02:18:13.043Z","updated_at":"2025-08-09T16:22:59.134Z","avatar_url":"https://github.com/MatrixAI.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# js-async-cancellable\n\nThis library provides the ability to cancel asynchronous tasks. Cancelling\nasynchronous tasks was never standardised in JavaScript. This was due to the\nmyriad complexity of cancellation illustrated by\nhttps://github.com/tc39/proposal-cancellation:\n\n\u003e The following are some architectural observations provided by **Dean Tribble**\n\u003e on the\n\u003e [es-discuss mailing list](https://mail.mozilla.org/pipermail/es-discuss/2015-March/041887.html):\n\u003e\n\u003e _Cancel requests, not results_\n\u003e\n\u003e Promises are like object references for async; any particular promise might be\n\u003e returned or passed to more than one client. Usually, programmers would be\n\u003e surprised if a returned or passed in reference just got ripped out from under\n\u003e them _by another client_. this is especially obvious when considering a\n\u003e library that gets a promise passed into it. Using \"cancel\" on the promise is\n\u003e like having delete on object references; it's dangerous to use, and unreliable\n\u003e to have used by others.\n\u003e\n\u003e _Cancellation is heterogeneous_\n\u003e\n\u003e It can be misleading to think about canceling a single activity. In most\n\u003e systems, when cancellation happens, many unrelated tasks may need to be\n\u003e cancelled for the same reason. For example, if a user hits a stop button on a\n\u003e large incremental query after they see the first few results, what should\n\u003e happen?\n\u003e\n\u003e - the async fetch of more query results should be terminated and the\n\u003e   connection closed\n\u003e - background computation to process the remote results into renderable form\n\u003e   should be stopped\n\u003e - rendering of not-yet rendered content should be stopped. this might include\n\u003e   retrieval of secondary content for the items no longer of interest (e.g.,\n\u003e   album covers for the songs found by a complicated content search)\n\u003e - the animation of \"loading more\" should be stopped, and should be replaced\n\u003e   with \"user cancelled\"\n\u003e - etc.\n\u003e\n\u003e Some of these are different levels of abstraction, and for any non-trivial\n\u003e application, there isn't a single piece of code that can know to terminate all\n\u003e these activities. This kind of system also requires that cancellation support\n\u003e is consistent across many very different types of components. But if each\n\u003e activity takes a cancellationToken, in the above example, they just get passed\n\u003e the one that would be cancelled if the user hits stop and the right thing\n\u003e happens.\n\u003e\n\u003e _Cancellation should be smart_\n\u003e\n\u003e Libraries can and should be smart about how they cancel. In the case of an\n\u003e async query, once the result of a query from the server has come back, it may\n\u003e make sense to finish parsing and caching it rather than just reflexively\n\u003e discarding it. In the case of a brokerage system, for example, the round trip\n\u003e to the servers to get recent data is the expensive part. Once that's been\n\u003e kicked off and a result is coming back, having it available in a local cache\n\u003e in case the user asks again is efficient. If the application spawned another\n\u003e worker, it may be more efficient to let the worker complete (so that you can\n\u003e reuse it) rather than abruptly terminate it (requiring discarding of the\n\u003e running worker and cached state).\n\u003e\n\u003e _Cancellation is a race_\n\u003e\n\u003e In an async system, new activities may be getting continuously scheduled by\n\u003e asks that are themselves scheduled but not currently running. The act of\n\u003e cancelling needs to run in this environment. When cancel starts, you can think\n\u003e of it as a signal racing out to catch up with all the computations launched to\n\u003e achieve the now-cancelled objective. Some of those may choose to complete (see\n\u003e the caching example above). Some may potentially keep launching more work\n\u003e before that work itself gets signaled (yeah it's a bug but people write buggy\n\u003e code). In an async system, cancellation is not prompt. Thus, it's infeasible\n\u003e to ask \"has cancellation finished?\" because that's not a well defined state.\n\u003e Indeed, there can be code scheduled that should and does not get cancelled\n\u003e (e.g., the result processor for a pub/sub system), but that schedules work\n\u003e that will be cancelled (parse the publication of an update to the\n\u003e now-cancelled query).\n\u003e\n\u003e _Cancellation is \"don't care\"_\n\u003e\n\u003e Because smart cancellation sometimes doesn't stop anything and in an async\n\u003e environment, cancellation is racing with progress, it is at most \"best\n\u003e efforts\". When a set of computations are cancelled, the party canceling the\n\u003e activities is saying \"I no longer care whether this completes\". That is\n\u003e importantly different from saying \"I want to prevent this from completing\".\n\u003e The former is broadly usable resource reduction. The latter is only usefully\n\u003e achieved in systems with expensive engineering around atomicity and\n\u003e transactions. It was amazing how much simpler cancellation logic becomes when\n\u003e it's \"don't care\".\n\u003e\n\u003e _Cancellation requires separation of concerns_\n\u003e\n\u003e In the pattern where more than one thing gets cancelled, the source of the\n\u003e cancellation is rarely one of the things to be cancelled. It would be a\n\u003e surprise if a library called for a cancellable activity (load this image)\n\u003e cancelled an unrelated server query just because they cared about the same\n\u003e cancellation event. I find it interesting that the separation between\n\u003e cancellation token and cancellation source mirrors that separation between a\n\u003e promise and it's resolver.\n\u003e\n\u003e _Cancellation recovery is transient_\n\u003e\n\u003e As a task progresses, the cleanup action may change. In the example above, if\n\u003e the data table requests more results upon scrolling, it's cancellation\n\u003e behavior when there's an outstanding query for more data is likely to be quite\n\u003e different than when it's got everything it needs displayed for the current\n\u003e page. That's the reason why the \"register\" method returns a capability to\n\u003e unregister the action.\n\nThis library attempts to address each concern.\n\n1. Cancel requests, not results - cancellation only affects the immediate\n   promise and all downstream promises, not upstream promises unless explicitly\n   configured via a signal handler\n2. Cancellation is heterogenous - cancellation can be customised through a\n   signal handler or an `AbortController`, this allows the user to define how\n   cancellation should propagate through the application\n3. Cancellation should be smart - during the `PromiseCancellable.then` binding,\n   both the the fulfilled and rejected handler takes the `signal: AbortSignal`,\n   here it is possible to customise the logic of fulfillment and rejection\n   depending on the situation, therefore cancellation can be as smart as you\n   want it to be\n4. Cancellation is a race - this is achieved by using `AbortSignal`, one cannot\n   ask if cancellation is finished, it is purely an event driven system\n5. Cancellation is \"don't care\" - `PromiseCancellation.cancel` is purely\n   advisory, the default behaviour is that the immediate promise is rejected\n   early, however this can be customised, therefore the default intention is\n   that the promise can be rejected with the cancellation reason, and means you\n   don't care about the result anymore, it does not imply anything stronger than\n   this\n6. Cancellation requires separation of concerns - by supplying a signal handler\n   or abort controller, it is possible to separate any concern\n7. Cancellation recovery is transient - during the `PromiseCancellable.then`,\n   `PromiseCancellable.catch` and `PromiseCancellable.finally`, the signal is\n   passed in, it is possible to change how you cancel depending on what the\n   current situation is.\n\n## Installation\n\n```sh\nnpm install --save @matrixai/async-cancellable\n```\n\n## Development\n\nRun `nix develop`, and once you're inside, you can use:\n\n```sh\n# install (or reinstall packages from package.json)\nnpm install\n# build the dist\nnpm run build\n# run the repl (this allows you to import from ./src)\nnpm run tsx\n# run the tests\nnpm run test\n# lint the source code\nnpm run lint\n# automatically fix the source\nnpm run lintfix\n```\n\n### Docs Generation\n\n```sh\nnpm run docs\n```\n\nSee the docs at: https://matrixai.github.io/js-async-cancellable/\n\n### Publishing\n\nPublishing is handled automatically by the staging pipeline.\n\nPrerelease:\n\n```sh\n# npm login\nnpm version prepatch --preid alpha # premajor/preminor/prepatch\ngit push --follow-tags\n```\n\nRelease:\n\n```sh\n# npm login\nnpm version patch # major/minor/patch\ngit push --follow-tags\n```\n\nManually:\n\n```sh\n# npm login\nnpm version patch # major/minor/patch\nnpm run build\nnpm publish --access public\ngit push\ngit push --tags\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatrixai%2Fjs-async-cancellable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatrixai%2Fjs-async-cancellable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatrixai%2Fjs-async-cancellable/lists"}