<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sub</title>
    <description>The latest articles on DEV Community by Sub (@sub).</description>
    <link>https://dev.to/sub</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F177202%2F4564b82c-d332-4a15-b1ed-95ff7ffb49a1.png</url>
      <title>DEV Community: Sub</title>
      <link>https://dev.to/sub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sub"/>
    <language>en</language>
    <item>
      <title>Meet canc, a complete lib for promise cancellation</title>
      <dc:creator>Sub</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:22:53 +0000</pubDate>
      <link>https://dev.to/sub/meet-canc-a-complete-lib-for-promise-cancellation-44bd</link>
      <guid>https://dev.to/sub/meet-canc-a-complete-lib-for-promise-cancellation-44bd</guid>
      <description>&lt;p&gt;In most modern async-heavy languages, cancellation is a first-class citizen. It really should be straightforward for a developer, but in JavaScript, gaining that level of control feels a lot like swimming against the current. I've spent a fair amount of time trying to address this gap.&lt;/p&gt;

&lt;p&gt;I didn't have a third-party library I could rely on, so I ended up building my own — and I suspect I'm probably not the only one who has traveled down that way. The resulting toolbag served me well in its rougher form and was battle-tested in-house for years before it was finally polished for a public release. Refactoring cleanup logic in our dashboard app to stop resource leaks was a huge win for cancelable promises. It convinced me they were the right tool for the job.&lt;/p&gt;

&lt;p&gt;This has been almost a decade-long journey for me to reach the stable release, both in terms of quality and features. It seems to have ended up in a pretty good place.&lt;/p&gt;

&lt;p&gt;The problem is the typical promise chain. You have a &lt;code&gt;fetch&lt;/code&gt; call that turns into a formatted report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reportPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;buildReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawReport&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawReport&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To halt the process, you usually have to mess with &lt;code&gt;AbortController&lt;/code&gt; or manual flag variables. But with &lt;code&gt;canc&lt;/code&gt; library, &lt;code&gt;reportPromise.cancel()&lt;/code&gt; just stops all involved tasks.&lt;/p&gt;

&lt;p&gt;Going from raw &lt;code&gt;then()&lt;/code&gt; chains to &lt;code&gt;async..await&lt;/code&gt; syntactic sugar requires adding some &lt;code&gt;yield*&lt;/code&gt; "salt" to achieve the same result — or, with cancellation, no result at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;canc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;canc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;canc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawReport&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reportPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getReport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;reportPromise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Stop all tasks at any point&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need &lt;code&gt;race()&lt;/code&gt;, &lt;code&gt;for await..of&lt;/code&gt;, and the rest of the bells and whistles? Welcome to the party, &lt;del&gt;then()&lt;/del&gt; then: &lt;a href="https://github.com/cancjs/canc" rel="noopener noreferrer"&gt;https://github.com/cancjs/canc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea of coupling generators with promises has been around since the beginning. Coroutine libraries like the renowned &lt;code&gt;co&lt;/code&gt; were a big deal in the pre-&lt;code&gt;async&lt;/code&gt; era. That &lt;code&gt;async..await&lt;/code&gt; is essentially built with generators and promises under the hood is hardly a coincidence.&lt;/p&gt;

&lt;p&gt;I started piecing this together around 2017. Back then, I had already approached a related problem with Angular. Native &lt;code&gt;async&lt;/code&gt; functions were fundamentally incompatible with Angular reactivity, backed by Zone.js. The potential solution was to rewire the semantics of &lt;code&gt;async..await&lt;/code&gt; with generators to get the control we needed instead of relying on a transpiler. Fortunately for the framework, this eventually resolved with the retirement of Zone.js. Reusing the same foundation for the cancellation mechanism became a reasonable development in my case. Bluebird's cancellation was already around, but it was orthogonal to native promises and &lt;code&gt;async..await&lt;/code&gt;. And since &lt;code&gt;async&lt;/code&gt; functions make promise-based control flow a breeze, a user can't be expected to give them away for nothing.&lt;/p&gt;

&lt;p&gt;The project spent a long time in limbo. Between fixing nasty bugs, unloading a few design footguns, paying off tech debt, and handling some copyright clearances, I had my hands full. That quiet period actually helped shape the library into what it is today. While the JS ecosystem is ever-changing, a few foundational pieces finally settled during that time. Once &lt;code&gt;AbortSignal&lt;/code&gt; became a cross-platform primitive, it was integrated deeper into the library for better interop. And as TypeScript became the industry standard, it became clear that the library had to be TS-first for good DX. This pushed me to finally solve &lt;a href="https://github.com/cancjs/canc/blob/master/packages/canc-coroutine/docs/yield-vs-yield-star.md#why-plain-yield-cannot-be-typed" rel="noopener noreferrer"&gt;the long-standing typing issues with generators&lt;/a&gt;, at least as best as the language currently permits.&lt;/p&gt;

&lt;p&gt;That's why you have to use &lt;code&gt;yield*&lt;/code&gt; instead of &lt;code&gt;yield&lt;/code&gt;. It's a necessary trade-off to ensure functional parity with &lt;code&gt;async..await&lt;/code&gt; while keeping the type system happy. Using &lt;code&gt;yield*&lt;/code&gt; is essentially a &lt;a href="https://github.com/cancjs/canc/tree/master/packages/canc-coroutine#why-yield-and-not-yield" rel="noopener noreferrer"&gt;known workaround for a typing limitation in generators&lt;/a&gt;. It forced us to ditch the eloquent &lt;code&gt;yield promise&lt;/code&gt; style of &lt;code&gt;co&lt;/code&gt; in favor of the more verbose &lt;code&gt;yield* canc.await(promise)&lt;/code&gt;. This adds a bit of syntactic overhead, but it's the way to guarantee the strict typing we all rely on today.&lt;/p&gt;

&lt;p&gt;It turned out to be the right call, especially since it aligns the &lt;code&gt;yield&lt;/code&gt;/&lt;code&gt;yield*&lt;/code&gt; distinction with the emitting vs. delegating semantics we deal with in async generator functions — &lt;a href="https://github.com/cancjs/canc/tree/master/packages/canc-coroutine#producing-an-async-iterable" rel="noopener noreferrer"&gt;&lt;code&gt;canc&lt;/code&gt; coroutines cover this too&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's next? 1.0 is a big milestone, but work continues. The &lt;a href="https://github.com/cancjs/canc/tree/master/packages/canc-unhandled-rejection" rel="noopener noreferrer"&gt;unhandled rejection package&lt;/a&gt; to reduce manual error handling has just been shipped. Beyond that, here are the nearest items on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web server middleware helpers.&lt;/strong&gt; Drafting support for &lt;a href="https://github.com/cancjs/canc/tree/master/examples/app-fullstack-cancel/server" rel="noopener noreferrer"&gt;Express&lt;/a&gt; and &lt;a href="https://github.com/cancjs/canc/tree/master/examples/app-fastify-mongoose" rel="noopener noreferrer"&gt;Fastify&lt;/a&gt;, with more frameworks on the way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ESLint plugin.&lt;/strong&gt; Rules to help navigate these new semantics properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async iterators toolbox.&lt;/strong&gt; Targeting functional parity with the &lt;a href="https://github.com/tc39/proposal-async-iterator-helpers" rel="noopener noreferrer"&gt;async iterator helpers proposal&lt;/a&gt;, but with full cancellation support baked in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React and Vue packages.&lt;/strong&gt; Helpers are available for evaluation in &lt;a href="https://github.com/cancjs/canc/tree/master/examples" rel="noopener noreferrer"&gt;React and Vue examples&lt;/a&gt;, working on improving them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Node.js package.&lt;/strong&gt; A drop-in replacement for built-in Node APIs, with functions both promisified and "cancelified" where it makes sense.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ES5-compatible cancelable promise.&lt;/strong&gt; Ensuring support for older runtimes and restricted environments.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find the library useful, or are at least interested in the approach. I'd be very grateful for any feedback or suggestions. I'm currently putting together a few more write-ups with real-world examples and in-depth details.&lt;/p&gt;

&lt;p&gt;Just to be sure, the repo is here: &lt;a href="https://github.com/cancjs/canc" rel="noopener noreferrer"&gt;https://github.com/cancjs/canc&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
