• bsimpson 4 days ago

    It's insane that "don't block the UI thread" is so complicated. I never would have expected half the article to be about an edge case if the user is in a background tab.

    What's worse is I don't even know how you're supposed to know what the edge cases are without stumbling into them. You shouldn't have to know about APIs like document.hidden unless you specifically want to handle background tabs differently. They shouldn't leak into the regular event loop.

    • Yoric 4 days ago

      It used to be even worse, with various tabs' event loops accidentally interacting with each other whenever a `alert()` was called.

      When we were (slowly, painstakingly) making the Firefox UI compatible with multi-thread and multi-process, we hit this kind of issues all the time. One of the reasons we introduced Promise and async/await (née task/yield) in JavaScript was to give us a fighting chance to at least look at the code and understand where it could all break. And then we had to come up with the tools to actually debug these...

      • severine 4 days ago

        But you did, and Firefox flies these days, thanks!

        • Yoric 3 days ago

          :)

        • ramon156 4 days ago

          Thanks for having made the internet at least a little bit more bearable!

          • Yoric 3 days ago

            :)

        • PittleyDunkin 4 days ago

          I just wanna note it's crazy that things confusing people a decade ago are still confusing people now. How fucking hard can the concept of a ui thread be?

          • hahn-kev 4 days ago

            I think part of the problem is that there is no other thread (outside of service workers), so that's why it's hard. In C# I would just fire up another thread to do the work and I don't have to worry about blocking the UI until I want to notify the UI from that thread. But I can't fire up a separate thread in JS so everything is done on the UI thread

            • Yoric 4 days ago

              I don't think it's the problem.

              I think that it's a combination of two things:

              1. many webdevs just have no clue what a thread is, because generally, they don't need it, so it isn't taught;

              2. most of the documentation you can find online was written by people (and now ChatGPT) who don't understand async, sprinkle the word randomly and tweak things until they seem to work.

              As a consequence, webdevs learn that async is magic. Which is a shame, because the underlying model is almost simple (the fact that we have both micro-tasks and tasks complicates things a bit).

              • mattmanser 3 days ago

                When your answer is "the problem is the developers", your answer is always wrong.

                Both your points, 1 + 2, are variations on that.

                The right answer is the problem is the language design.

                • Yoric 3 days ago

                  My points are "the problem is it isn't taught" and "the problem is the documentation available", so I'm not sure how you read "the problem is the developers".

                • bsuvc 4 days ago

                  > many webdevs just have no clue what a thread is, because generally, they don't need it, so it isn't taught

                  JavaScript doesn't have threads.

                  • soulofmischief 4 days ago

                    Parent commenter helped implement async in JS, they know what they are talking about. JS has threads locked behind semantics. Web workers run on separate threads. I do a lot of heavy parallel processing that never blocks the UI with them all the time.

                    • bsuvc 4 days ago

                      I stand corrected.

                      It looks like web workers is the way for JavaScript to do multi-threading.

                      Async has always been enough for what I need to do in the front end, as most of my long running processes are just calling a back end.

                      Edit to add: for context, I am a full stack developer and know what threads are... I just never have needed them in the browser.

                      • soulofmischief 4 days ago

                        Web workers are great for local compute and isolation. Unfortunately it's a hassle managing sane pooling because different platforms have different worker limits.

                        On the other hand, the isolation guarantees are strong. There aren't really any footguns. Messaging is straightforward, works with a lot of data types and supports channels for inter-worker communication.

                    • Retr0id 4 days ago

                      This is not meaningfully true in 2024

                      • Retr0id 4 days ago

                        *2025 (although still true of 2024 and several preceding years, heh)

                  • chii 4 days ago

                    The browser as a runtime is the epitome of worse-is-better.

                    • PittleyDunkin 4 days ago

                      [flagged]

                    • rafram 4 days ago

                      Outside of workers, of which service workers are one type.

                      • bfgeek 3 days ago

                        You can create a new thread via. `new Worker` but using a worker requires a separate file, and lots of serialisation code as you communicate via `postMessage`. TC39 module expressions helps but not a lot of movement recently. https://github.com/tc39/proposal-module-expressions?tab=read...

                        • bakkoting 3 days ago

                          There's some progress on that proposal, just happening elsewhere. https://github.com/tc39/proposal-esm-phase-imports defines machinery for importing a ModuleSource object which you can instantiate as a Worker, and once that's done module expressions would just be syntax which evaluates to a ModuleSource rather than needing a separate import.

                        • PittleyDunkin 4 days ago

                          > But I can't fire up a separate thread in JS so everything is done on the UI thread

                          Yea targeting the web is nuts if you have other options!

                        • moregrist 4 days ago

                          To be fair, blocking the UI thread is still a common issue in desktop app programming, where it’s been a well-known thing for at least 1-2 decades longer.

                          You either have to split linear execution (hard although async can make this easier in languages like Python) or go multithreaded with its own difficulties.

                          But IME what typically happens, at least in desktop apps, is that doing a little work in the UI thread is fine until it isn’t, and the “not fine” moment can be months or years away from when the code is written. The bug reports are often vague and rare enough that it takes a while to see the pattern (eg: “it froze and then I killed the program”). And after enough of these problems, someone with experience puts a framework and some rules in place that move tasks off the UI thread. Which works until they creep back in.

                          • Ma8ee 4 days ago

                            I read somewhere that about 50% of all developers have less than 5 years of experience. That fact explains quite a lot.

                            • greggyb 4 days ago

                              I’ve come across that as well, but in presentations. I think it is either Alan Kay or Bob Martin who likes to use the statistic. I recall it being framed in terms of the growth of tech/programming as a profession: it’s doubled every 5 years; the fact that 50% of developers have less than 5 years of experience. This is simply exponential growth stated in two different ways.

                              I do a lot of work on a platform that has doubled in user base yearly since its inception, for about a decade (though I think we are at an inflection point now or soon), and it is wild to have “experts” be those with 2 or 3 years of experience. Having used the platform for 11 years, now it is crazy to believe I have more experience than 99.9% of the field.

                            • WJW 4 days ago

                              I don't think that is weird at all. Nobody is born knowing about UI threads and junior devs today are not significantly different in knowledge from junior devs a decade ago. It's not surprising that things which were confusing in 2015 are still confusing now.

                              • santoshalper 4 days ago

                                That's what I love about these junior devs, man. I get more experienced, but they stay noobs. All right, all right, all right.

                              • undefined 4 days ago
                                [deleted]
                                • normie3000 4 days ago

                                  > things confusing people a decade ago

                                  I was being confused by blocked UI threads TWO decades ago (AWT or Swing or something), so I'm confused that it was confusing you one decade ago.

                                  • schwartzworld 4 days ago

                                    Hopefully it’s not the same people getting confused.

                                • gttalbot 4 days ago

                                  What's old is new again. Some of the same techniques used to keep pre-MacOS-X applications responsive, back when MacOS was cooperatively scheduled, show up here.

                                  This begs the question of what is a reasonable programming model? In the MacOS case, the forcing function was buying NeXT, using their Unix kernel for MacOS, and literally firing the OS engineers who disagreed with preemptive multitasking.

                                  For these browsers, is there a programming model that could be instituted where processing in these handlers didn't hold up the main UI thread?

                                  • wongarsu 4 days ago

                                    The previous decade certainly feels like a big resurgence of cooperative multitasking, in the rise of JavaScript and the rise of async in all languages.

                                    Making JavaScript (conceptually) runs in the UI thread was imho one of the mistakes owed to the extremely simple early versions of JavaScript. We would be better off if JavaScript was preemptively scheduled (whether by the browser or by the OS) with language primitives to lock the UI in specific execution sections.

                                    • ciconia 4 days ago

                                      Locking the UI while doing a lengthy operation is hardly an acceptable solution. A better solution would be to indicate to the user an async operation is in progress, and optionally provide a button for canceling the operation.

                                      • wongarsu 4 days ago

                                        What I was thinking about is that you do need a way to lock the UI when running multiple statements that update the UI. Something like a lockUI{} block (conceptually a critical section holding a lock on UI updates). This would for example allow you to prevent a situation where you have to change two data attributes on a button and the user clicks the bottom between those two updates. It would be on the programmer to keep those lockUI{} sections as short as possible.

                                        If JavaScript 1.0 had included such a primitive you could run all other JavaScript in the background. Alas, the JavaScript we have is essentially putting all code into such an lockUI block, and this assumption is baked in to a lot of code

                                        • pavlov 4 days ago

                                          If that language feature had been included, the early web would have been filled with tutorials that say: “Don’t forget to wrap all your code in lockUI{} because that guarantees it runs correctly and things don’t suddenly change behind your back!”

                                          And then we’d have the popular web frameworks just taking the lock and running all user code inside it, and everything would be the same as today.

                                          • greggyb 4 days ago

                                            But we’d have options. ~There aren’t any today.~

                                            Ninja edit: there haven’t been any until workers.

                                  • leeoniya 4 days ago

                                    the problem is that async-ification infects everything up the call chain. converting a synchronous loop to async may now require that you change a lot of your sync code above that loop into async code as well, which can complicate all sorts of stuff like debugging, profiling, error handling.

                                    for this reason i always optimize the shit out of all synchronous code before resorting to async. and if you do need to go async after that, you might be better off just offloading that optimized synchronous op to a worker thread (or to wasm) to not block the UI.

                                    modern JS vms are insanely fast when you pay attention to GC pressure, mem allocation, monomorphism, and dont use accidentally quadratic algos. it's rare that i encounter a codebase that can't be sped up by some large multiple without ever resorting to an async/defer crutch.

                                    • MzHN 4 days ago

                                      If this was any other language than JS I would agree but my personal experience with JS is the opposite.

                                      In my experience almost everything in the JS world is already async. User interactions are async, requests are async, almost all NodeJS APIs are async. To me having to add more async in JS is a tiny barrier compared to what I'm facing in other languages that feel more synchronous to me.

                                      Since there is already so much async I also feel like debugging, profiling and error handling are all pleasantly solved problems in JS.

                                      Offloading to workers is also async so while there are many valid benefits to be gained, avoiding async does not seem like one of them to me.

                                      • StackTopherFlow 3 days ago

                                        I agree with you. I rarely find myself in a situation where a piece of async code forces me to refactor a synchronous code to be async.

                                        A lot of junior devs I've worked with don't understand that putting `async` in front of a function doesn't actually make it asynchronous.

                                        • leeoniya 2 days ago

                                          > A lot of junior devs I've worked with don't understand that putting `async` in front of a function doesn't actually make it asynchronous.

                                          of course it does. annotating any function with async makes it implicitly return a Promise, which fundamentally changes how all callers have to use it (and their caller's callers, etc.). you can't "just" make a function async and change nothing about how it was used previously.

                                          https://jsfiddle.net/om3tj2rd/

                                            async function foo() {
                                              return 2;
                                            }
                                            
                                            console.log(foo());
                                          
                                          this
                                          • leeoniya 20 hours ago

                                            > of course it does.

                                            i should clarify a bit, that this can still freeze your UI if foo() is expensive, since the microtask still runs on the same event loop. my point is that you cannot always throw async in front of a function and not change any other code.

                                      • fenomas 4 days ago

                                        > you might be better off just offloading that optimized synchronous op to a worker thread (or to wasm) to not block the UI.

                                        It works in principle, but note that this really complicates your build process. In particular, if you're writing a library that other people will use as a dependency, there's really no good way to use workers at all without affecting how people bundle their code using your library.

                                        • adregan 4 days ago

                                          The library use case is trickier, but bundlers do a pretty good job of handling workers (albeit with funky magic comments in webpack’s case).

                                          What I find a pain is the uneven support for shared workers.

                                        • ordu 4 days ago

                                          > you might be better off just offloading a synchronous op to a worker thread to not block the UI.

                                          I believe it should be the answer. If your computations are tolerably fast, then you could do it without async, but if they are not, then it is better to use preemptive multitasking for them. The overhead on the kernel scheduler will be small, because you don't start 10k of threads concurrently eating CPU time. Probably the overhead of starting a thread doesn't matter either with long tasks. As a bonus you could also do blocking i/o operations without thinking about blocking.

                                          • mikepurvis 4 days ago

                                            Is this a JS specific issue? I find python is decently friendly to having little pockets of async where it makes sense in what is otherwise a regular synchronous program.

                                            • Too 4 days ago

                                              I'd say it's the other way around. In JS, async is just syntax sugar on Promises, they still execute within the same event loop. So regardless of if you are in async or not, you always have to think about not blocking. This becomes a lot easier to reason about, because all code is from the beginning made to be non blocking. Whereas in python if you call a blocking sync function from async world you are up for trouble. The problem solved in the OP is the unusual case where you need to do some big sync computation.

                                              • Yoric 4 days ago

                                                My experience is that Python is worse at async than JavaScript. At least, the debugging experience quickly scales up to nightmarish.

                                              • bboygravity 4 days ago

                                                Does anyone know when we went from calling things serial and parallel execution to sync and async?

                                                Was it before or after we started calling "man-hours" "story points"?

                                                • Yoric 4 days ago

                                                  Async and parallel are a bit different.

                                                  Parallel means that things execute at the same time (or at least appear to do so). Async means that you yield control to some kind of scheduler and get it back at a later point.

                                                  Barring workers, JavaScript actually guarantees that two pieces of code never execute at the same time. That's the run-to-completion semantics.

                                                  When async was introduced to JavaScript from two different angles (callbacks in Node, Promise then await in browsers), there was limited parallelism involved (typically running I/O in the background), but the async was meant to ensure that:

                                                  1. while a task was (almost explicitly) waiting, another one could run;

                                                  2. in the case of the browser, we could yield control from JavaScript often enough that the UI could run smoothly.

                                                  • pavlov 4 days ago

                                                    Node didn’t introduce callbacks to JavaScript, they were present in the earliest browser APIs (img.onload, etc.)

                                                    • Yoric 4 days ago

                                                      Fair enough, I meant CPS-style programming.

                                                      • layer8 4 days ago

                                                        These were just regular events on the UI thread, not any different from onclick etc., IINM.

                                                        • pavlov 4 days ago

                                                          So, the same thing as callbacks in Node.

                                                          • fenomas 4 days ago

                                                            When people talk about a callback in this kind of context they usually mean one function passed as an argument to another, in order to be invoked with the latter function's return value. Not event handlers like onclick, etc.

                                                            • layer8 4 days ago

                                                              I’m not familiar with Node, but static event handlers aren’t usually referred to as callbacks in that context. “Callback” implies that something is being called, for it to call back to the client code. That’s not what’s happening with the onxxx event handlers. There is no “back” there. The event loop simply calls “forward” into the JavaScript code.

                                                              • pavlov 4 days ago

                                                                I don’t know what you mean by “forward” because there is no difference between these two:

                                                                  img.onload = function() {…}
                                                                  img.src = “some url”
                                                                
                                                                  img.load(“some url”, function() {…})
                                                                
                                                                The early JavaScript APIs use the first style, but the result is the same.
                                                      • rplnt 4 days ago

                                                        Async and parallel are not the same thing. You can run code async and yet not parallel (one core, no i/o).

                                                        • com2kid 4 days ago

                                                          Async in JS is not in parallel, which is a very -very- important distinction for program correctness.

                                                          • abtinf 4 days ago

                                                            JS, as typically run in a browser, is both async and single-threaded.

                                                            • johannes1234321 4 days ago

                                                              Parallel code runs parallel at the same time (on different CPU cores etc.)

                                                              Async code is scheduling different parts after each other. It's still running in a single thread (maybe on same core)

                                                              aaync essentially is non-blocking IO in many cases. It can also ahieldnsome logic running in a diffefent thread, hidden from that segment of code.

                                                              • 333c 4 days ago

                                                                I think your explanation of async, while true, doesn't get at what's special about async. The explanation seems true of (non-parallel) concurrency as well (for example, thread scheduling on a one-core CPU).

                                                          • dleeftink 4 days ago

                                                            For really heavy work I think hamsters.js is pretty cool[0]. GPU.js can also make a huge difference, but might be overkill for some applications.

                                                            [0]: https://github.com/austinksmith/Hamsters.js

                                                            • jebarker 4 days ago

                                                              As others have commented, this seems way too hard to achieve. But it warms my heart to see web developers caring about performance.

                                                              • astoilkov 3 days ago

                                                                For the past 4 years, I've been working on a library that does exactly this — https://github.com/astoilkov/main-thread-scheduling.

                                                                It works in all browsers and it breaks long tasks. Sometimes it's even more powerful than scheduler.yield().

                                                                HN post: https://news.ycombinator.com/item?id=38864791.

                                                                • niutech 3 days ago

                                                                  Why not use async iterators and generators in JS? https://javascript.info/async-iterators-generators

                                                                  • thdhhghgbhy 4 days ago

                                                                    I'm surprised the solution ended up having a magic number in it. I realise the initial guess is refined, but in a commercial scenario I see this code aging poorly and misbehaving at some point, after the whizkid who wrote it has left.

                                                                    • geon 4 days ago

                                                                      Is anyone using generators and the yield keyword to do concurrency in js?

                                                                      • underyx 4 days ago

                                                                        [flagged]

                                                                        • Brian_K_White 4 days ago

                                                                          Made sense to me. A problem with multiple solutions, but none of them actually good, or as good as not having the problem.

                                                                          If you didn't understand that, that would be embarassing. If you did understand it, then it wasn't nonsense.

                                                                          Humans have been using weak analogies for several millenia longer than any AI has existed.

                                                                          • bsimpson 4 days ago

                                                                            Dev rel content has always had analogies in it.

                                                                            It's a big assumption that AI made that choice.

                                                                            • pwdisswordfishz 4 days ago

                                                                              It's called a "benefit of the doubt"...

                                                                          • neuroelectron 4 days ago

                                                                            [flagged]

                                                                            • aktuel 4 days ago

                                                                              These issues pop up everywhere, not just with JS. You can (and people do) write horrendously complex and laggy software (e.g. using "frameworks") in any language. Lots of excellent pieces of software are written in pure JS.

                                                                              • undefined 4 days ago
                                                                                [deleted]
                                                                              • bennythomsson 4 days ago

                                                                                Isn't the whole approach wrong? All that stuff should be in another thread. That's it. Re-inventing a bad version of cooperative multitasking, in 2025, really?

                                                                                It's surely a nice exercise, but I really hope this is not production code anywhere. If it is then I'm not surprised about the current state of web software (myself working in a very different area, so I don't really have a clue).