[HN Gopher] You can't cancel a JavaScript promise (except someti...
       ___________________________________________________________________
        
       You can't cancel a JavaScript promise (except sometimes you can)
        
       Author : goodoldneon
       Score  : 69 points
       Date   : 2026-04-07 13:34 UTC (9 hours ago)
        
 (HTM) web link (www.inngest.com)
 (TXT) w3m dump (www.inngest.com)
        
       | dimitropoulos wrote:
       | > Libraries like Effect have increased the popularity of
       | generators, but it's still an unusual syntax for the vast
       | majority of JavaScript developers.
       | 
       | I'm getting so tired of hearing this. I loved the article and
       | it's interesting stuff, but how many more decades until people
       | accept generators as a primitive??
       | 
       | used to hear the same thing about trailing commas, destructuring,
       | classes (instead of iife), and so many more. yet. generators
       | still haven't crossed over the magic barrier for some reason.
        
         | yeittrue wrote:
         | Generators peaked in redux- saga and thunk days before we had
         | widespread support for async/await.
         | 
         | You're right, mostly pointless syntax (along with Promise) now
         | that we can await an async function anyway, especially now with
         | for .. of to work with Array methods like .map
         | 
         | But there are still some use cases for it, like with Promise.
         | Like for example, making custom iterators/procedures or a
         | custom delay function (sync) where you want to block execution.
        
         | horsawlarway wrote:
         | There just aren't that many spots where the average js dev
         | actually needs to touch a generator.
         | 
         | I don't really see generators ever crossing into mainstream
         | usage in the same way as the other features you've compared
         | them to. Most times... you just don't need them. The other
         | language tools solve the problem in a more widely accessible
         | manner.
         | 
         | In the (very limited & niche) subset of spots you do actually
         | need a generator, they're nice to have, but it's mostly a
         | "library author" tool, and even in that scope it's usage just
         | isn't warranted all that often.
        
           | gbuk2013 wrote:
           | It is a specialised instrument but a useful one: batch
           | processing and query pagination are first class use cases for
           | generators that can really simplify business logic code.
           | Stream processing is another and in fact Node.js streams have
           | had a generator API for several releases now.
        
           | no_wizard wrote:
           | mainly because they messed up on implementation, in two ways.
           | This is of course my opinion.
           | 
           | The first being `.next()` on the returned iterators. If you
           | pass an argument to it, the behavior is funky. The first time
           | it runs, it actually doesn't capture the argument, and then
           | you can capture the argument by assigning `yield` to a
           | variable, and do whatever, but its really clunky from an
           | ergonomic perspective. Which means using it to control side
           | effects is clunky.
           | 
           | The second one how it is not a first class alternative to
           | Promise. Async Generators are not the most ergonomic thing in
           | the world to deal with, as you have the issues above plus you
           | have to await everything. Which I understand why, but because
           | generators can't be used in stead of Promises, you get these
           | clunky use cases for using them.
           | 
           | They're really only useful as a result, for creating custom
           | iterator patterns or for a form of 'infinite stream' returns.
           | Beyond that, they're just not all that great, and it often
           | takes combining a couple generators to really get anything
           | useful out of them.
           | 
           | Thats been my experience, and I've tried to adopt generators
           | extensively a few times in some libraries, where I felt the
           | pattern would have been a good fit but it simply didn't turn
           | out most of the time.
        
             | cowboyd wrote:
             | I think writing an effect library yourself is a tough ask,
             | but some of them have gotten really, really good. And they
             | get you things that are simply not possible with promise.
             | Check out Effection if you want a more vanilla javascript
             | syntax, or Effect if you're really into expressing things
             | functionally.
        
       | game_the0ry wrote:
       | Off topic, but that site has really nice design
        
         | williamdclt wrote:
         | Mh, I couldn't read due to the huge contrast and had to switch
         | to reader mode, so...
        
           | jazzypants wrote:
           | I personally find it to be perfectly readable. I've heard of
           | people with issues with white text on a black background, but
           | I don't fully understand it. Do you have astigmatism?
        
           | game_the0ry wrote:
           | I mean, I'm not a designer but it was interesting enough to
           | call out.
        
           | seattle_spring wrote:
           | What colors were you seeing? It's light white text on a black
           | background for me-- both super common and plenty readable.
        
           | Pay08 wrote:
           | Really? I generally very much like to have a lot of contrast,
           | but too much can definitely hurt my eyes.
        
       | pjc50 wrote:
       | I like how C# handles this. You're not forced to support
       | cancellation, but it's strongly encouraged. The APIs all take a
       | CancellationToken, which is driven by a CancellationTokenSource
       | from the ultimate caller. This can then either be manually
       | checked, or when you call a library API it will notice and throw
       | an OperationCancelledException.
       | 
       | Edit: note that there is a "wrong" way to do this as well. The
       | Java thread library provides a stop() function. But since that's
       | exogenous, it doesn't necessarily get cleaned up properly. We had
       | to have an effort to purge it from our codebase after discovering
       | that stopping a thread while GRPC was in progress broke all
       | future GRPC calls from all threads, presumably due to some shared
       | data structure being left inconsistent. "Cooperative" (as opposed
       | to preemptive) cancel is much cleaner.
        
         | CharlieDigital wrote:
         | C# has very good support for this.
         | 
         | You can even link cancellation tokens together and have
         | different cancellation "roots".
        
         | esprehn wrote:
         | AbortSignal is same thing on the Web. It's unfortunate TC39
         | failed to ever bring a CancelToken to the language to
         | standardize the pattern outside browsers.
        
           | runarberg wrote:
           | TC39 seems to be failing at many things for the past 10
           | years.
        
             | notnullorvoid wrote:
             | Hard disagree, TC39 has done great work over the last 10
             | years. To name a few: - Async/await - Rest/spread - Async
             | iterators - WeakRefs - Explicit Resource Management -
             | Temporal
             | 
             | It's decisions are much more well thought out than WHATWG
             | standards. AbortSignal extending from EventTarget was a
             | terrible call.
        
               | runarberg wrote:
               | many things !== all the things
               | 
               | More good works from the last 10 years includes .at(),
               | nullish chaining, BigInt etc.
               | 
               | But most of what you mentioned is closing in on 10 years
               | in the standard (Async/Await is from 2017) meaning the
               | bulk of the work done is from over 10 years ago.
               | 
               | The failure of AbortSignal is exactly the kind of failure
               | TC39 has been doing in bulk lately. I have been following
               | the proposal to add Observables to the language, which is
               | a stage 1 proposal (and has been for over 10 years!!!).
               | There were talks 5 years ago (!) to align the API with
               | AbortSignal[1] which I think really exemplifies the
               | inability for TC39 to reach a workable decision (at least
               | as it operates now).
               | 
               | Another example I like to bring up are the failure of the
               | pipeline operator[2], which was advanced to stage-2 four
               | years ago and has been in hiatus ever since with very
               | little work to show for it. After years of deliberation
               | very controversal version of the operator with a massive
               | community backlash. Before they advanced it it was one of
               | the more popular proposals, now, not so much, and
               | personally I sense any enthusiasm for this feature has
               | pretty much vanished. In other words I think they took
               | half a decade to make the obviously wrong decision, and
               | have since given up.
               | 
               | From the failure of the pipeline operator followed a
               | bunch of half-measures such as array grouping, and
               | iterator helpers etc. which could have easily been
               | implemented in userland libraries if the more functional
               | version of the pipeline operator would have advanced.
               | 
               | 1: https://github.com/tc39/proposal-observable/issues/209
               | 
               | 2: https://github.com/tc39/proposal-pipeline-operator
        
           | bakkoting wrote:
           | Browsers have said that they are unwilling to ship any new
           | cancelation mechanisms given that AbortSignal already exists,
           | so we can't ship a different CancelToken. But I think there's
           | a path to standardizing a subset of the existing AbortSignal
           | machinery [1].
           | 
           | (I am on TC39 and while this isn't my highest priority I did
           | bring the topic for discussion at the last meeting [2], and
           | there was support from the rest of committee.)
           | 
           | [1] https://github.com/tc39/proposal-concurrency-
           | control/issues/...
           | 
           | [2] https://github.com/bakkot/structured-concurrency-for-js
        
         | teraflop wrote:
         | I am surprised that you had to go out of your way to remove
         | Thread.stop from existing Java code. It's been deprecated since
         | 1998, and the javadoc page explains pretty clearly why it's
         | inherently unsafe.
         | 
         | It's hard to miss all the warnings unless you're literally just
         | looking at the method name and nothing else.
        
           | pjc50 wrote:
           | I was certainly surprised to see it when I found it.
        
           | wiseowise wrote:
           | That's barely-junior interview question indeed.
        
           | AtlasBarfed wrote:
           | One of Java's the ecosystem fundamental platforms is that
           | it's multi-threading. It's gone through too many models.
           | 
           | And since Java has a metric ton of blog posts from the 2000s
           | and 2010s, a lot of search engines lead you to older
           | models.java itself has gone from green threads to OS threads
           | and back to green threads now.
        
           | kelnos wrote:
           | Not to mention that I feel like it's pretty unusual to be
           | creating and managing threads yourself in Java these days,
           | instead of using a thread pool/executor.
        
         | wiseowise wrote:
         | > "Cooperative" (as opposed to preemptive) cancel is much
         | cleaner.
         | 
         | Which what Thread.interrupt does.
        
         | torginus wrote:
         | I don't like it - you're forced to pass around this token,
         | constantly manage the lifecycle of cancellation sources - and
         | incredibly bug prone thing in async context, and it quickly
         | gets very confusing when you have multiple tokens/sources.
         | 
         | I understand why they did it - a promise essentially is just
         | some code, and a callback that will be triggered by someone at
         | some point in time - you obviously get no quality of service
         | promises on what happens if you cancel a promise, unless you as
         | a dev take care to offer some.
         | 
         | It's also obvious that some operations are not necessarily
         | designed to be cancellable - imagine a 'delete user' request -
         | you cancelled it, now do you still have a user? Maybe, maybe
         | you have some cruft lying around.
         | 
         | But still, other than the obvious wrong solution - C# had a
         | Thread.Abort() similar to the stop() function that you
         | mentioned, that was basically excommunicated from .NET more
         | then a decade ago, I'm still not happy with the right one.
        
           | CharlieDigital wrote:
           | > ...constantly manage the lifecycle of cancellation sources
           | 
           | Very rare unless you are spawning your own.
           | 
           | Usually, you are passing through a runtime provided token
           | (e.g. ASP.NET).
        
             | torginus wrote:
             | Not that rare in my experience, I constantly had to write
             | software like this. Not every day, but it certainly did
             | come up quite often in my code and others'
             | 
             | Oh and oone more thing - the very (developer-managed)
             | complexity makes it that people constantly got it wrong,
             | usually just enough (as often with the case of threading)
             | that it worked fine 90% of the time, and was very hard to
             | make a case to management why we should invest effort into
             | fixing it.
        
             | estimator7292 wrote:
             | Not rare in the slightest. C# is used in a _lot_ of places
             | that aren 't the web and don't have extra frameworks piled
             | on.
             | 
             | If you're writing a bare C# library for desktop deployment,
             | you're managing your own cancellation sources.
        
           | estimator7292 wrote:
           | Cancelling a token doesn't immediately abort the underlying
           | Task. It is up to the implementation of that task to _poll_
           | the token and actively decide when to abort.
           | 
           | In your example, you'd design your delete task such that if
           | you want it to be cancelable, it can only be canceled before
           | data is modified. You simply don't abort in the middle of a
           | database transaction.
           | 
           | Moreover, because of the way cancellation tokens work, you
           | _can 't_ abort blocking function calls unless you also pass
           | the token along. There just isn't a mechanism that can
           | interrupt a long IO operation or whatever unless you
           | explicitly go to the effort to make that happen.
           | 
           | A cancellation token is more of a "pretty please stop what
           | you're doing when you feel like it" concept than
           | Thread.Abort().
        
       | cush wrote:
       | GC can be very slow. Relying on it for control flow is a bold
       | move
        
         | BlueGreenMagick wrote:
         | I don't think the control flow relies on GC.
         | 
         | The control flow stops because statements after `await new
         | Promise(() => {});` will never run.
         | 
         | GC is only relied upon to not create a memory leak, but you
         | could argue it's the same for all other objects.
        
         | augusto-moura wrote:
         | Not _that very_ slow for web applications. Maybe for real time
         | or time-sensitive applications. For most day to day web apps GC
         | pauses are mostly unnoticeable, unless you are doing something
         | very wrong
        
         | dominicrose wrote:
         | as long as there's no leak interrupting a promise should be
         | good for performance overall, not necessarily for the front-end
         | but for the whole chain.
        
       | eithed wrote:
       | > Promise itself has no first-class protocol for cancellation,
       | but you may be able to directly cancel the underlying
       | asynchronous operation, typically using AbortController.
       | 
       | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
        
       | mohsen1 wrote:
       | Back in 2012 I was working on a Windows 8 app. Promises were
       | really only useful on the Windows ecosystem since browser support
       | was close to non existent. I googled "how to cancel a promise"
       | and the first results were Christian blogs about how you can't
       | cancel a promise to god etc. Things haven't changes so much
       | since, still impossible to cancel a promise (I know AbortSignal
       | exists!)
        
       | abraxas wrote:
       | and so the thirty year old hackathon continues...
        
       | afarah1 wrote:
       | You can also race it with another promise, which e.g. resolves on
       | timeout.
        
         | Keyframe wrote:
         | You can but it still won't get cancelled. I found out when I
         | tried to implement a hard time limit to a call.
        
       | thomasnowhere wrote:
       | The never-resolving promise trick is clever but what caught me
       | off guard is how clean the GC behavior is. Always assumed hanging
       | promises would leak in long-lived apps but apparently not as long
       | as you drop the references.
        
       | bastawhiz wrote:
       | Be careful with this, though. If a promise is expected to resolve
       | and it never does, and the promise needs to resolve or reject to
       | clean up a global reference (like an event listener or interval),
       | you'll create a memory leak. It's easy to end up with a leak
       | that's almost impossible to track down, because there isn't
       | something obvious you can grep for.
        
         | hungryhobbit wrote:
         | This is addressed at the end of the article:
         | The catch            You're relying on garbage collection,
         | which is nondeterministic. You don't get to know when the
         | suspended function is collected. For our use case, that's fine.
         | We only need to know that it will be collected, and modern
         | engines are reliable about that.            The real footgun is
         | reference chains. If anything holds a reference to the hanging
         | promise or the suspended function's closure, the garbage
         | collector can't touch it. The pattern only works when you
         | intentionally sever all references.
        
           | bastawhiz wrote:
           | That should honestly be much higher up and much more clearly
           | spelled out.
        
       | shadowgovt wrote:
       | I soft of feel like every five years someone comes along and
       | tries to re-invent cancellable threads and immediately arrives
       | back at the same conclusion: the problem of what it means to
       | "cancel" a thread is so domain-specific that you never save
       | anything trying to "support" it in your threading framework; you
       | try and save people the effort of doing something ad-hoc to
       | simulate cancellation and build something at _least_ as
       | complicated as what they would build ad-hoc, because thread
       | cancellation is too intimately tied to the problem domain the
       | threads are operating on to generalize it.
        
         | fragmede wrote:
         | But what if we add _another_ layer of indirection.
        
       | littlestymaar wrote:
       | Ten years ago, I was an acid reader of the tc39 (EcmaScript
       | standard committee) mailing list and cancelable promises used to
       | be the hot topic for a while.
       | 
       | I unsubscribed at some point because I wasn't working with
       | JavaScript this much, but it's disappointing to see that this
       | work has gone nowhere in the meantime.
       | 
       | I like how Rust futures are canceled by dropping them, even
       | though it's also a footgun (though IMHO this is more of a problem
       | with the _select!_ pattern than with drop-to-cancel proper).
        
       | cowboyd wrote:
       | Is it safe to just "stop calling next() on a generator?" like the
       | post suggest?
       | 
       | To me that sounds like dropping the task on the floor.
       | Specifically, this will not invoke any finally {} blocks:
       | 
       | More correctly, you should invoke `return()` on the generator.
       | Otherwise, you won't provide execution guarantees. This is how
       | Effection does it. There is no equivalent in async functions, so
       | it sounds like the same problem would apply to the GC technique.
        
       | notnullorvoid wrote:
       | The argument against rejecting to cancel seems like a stretch to
       | me. It's completely fine if you view cancellation as a error
       | condition, it allows you to recover from a cancellation if you
       | want (swallow the error w catch) or to propagate it.
        
       ___________________________________________________________________
       (page generated 2026-04-07 23:01 UTC)