[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)