Live data from Hacker News

A better streams API is possible for JavaScript

blog.cloudflare.com

81–90 of 167 posts

Re: A better streams API is possible for JavaScript

#81

Async iterables aren't necessarily a great solution either because of the exact same promise and stack switching overhead - it can be huge compared to sync iterables. If you're dealing with small objects at the production side, like individual tag names, attributes, bindings, etc. during SSR., the natural thing to do is to just write() each string. But then you see that performance is terrible compared to sync iterab…

Yeah that problem you have is pretty much what I'm offering a solution to. It's the same thing you're already doing but more robust. Also I'm curious why you say that generators are far too slow. Were you using async generators perhaps? Here's what I cooked up using sync generators: https://github.com/bablr-lang/stream-iterator/blob/trunk/lib... This is the magic bit: return step.value.then((value) => { return this.n…

You know now that I look at it I do think I need to change this code to defend better against multiple eager calls to `next()` when one of them returns a promise. With async generators there's a queue built in but since I'm using sync generators I need to build that defense myself before this solution is sound in the face of next();next(). That shouldn't be too hard though.

Re: A better streams API is possible for JavaScript

#82

Earlier quoted context omitted.

> Obviously I'm gonna be biased, but I'm pretty sure my version is also objectively superior: > - I can easily make mine from theirs That... doesn't make it superior? On the contrary, theirs can't be easily made out of yours, except by either returning trivial 1-byte chunks, or by arbitrary buffering. So their proposal is a superior primitive. On the whole, I/O-oriented iterators probably should return chunks of T, o…

> So their proposal is a superior primitive. This lines up with my thinking. The proposal should give us a building block in the form of the primitive. I would expect the grandparent comment’s API to be provided in a library built on top of a language level primitive.

How would you then deal with a stream of UTF8 code points? They won't fit in a UInt8Array. There will be too many for async iterators to perform well: you'll hit the promise thrashing issues discussed in the blog post

Re: A better streams API is possible for JavaScript

#83
Promises should not be a big overhead. If they are, that seems like a bug in JS engines.

At a native level (C++/rust), a Promise is just a closure added to a list of callbacks for the event loop. Yes, if you did 1 per streamed byte then it would be huge but if you're doing 1 promise per megabyte, (1000 per gig), it really shouldn't add up 1% of perf.

Re: A better streams API is possible for JavaScript

#84

Earlier quoted context omitted.

> So their proposal is a superior primitive. This lines up with my thinking. The proposal should give us a building block in the form of the primitive. I would expect the grandparent comment’s API to be provided in a library built on top of a language level primitive.

How would you then deal with a stream of UTF8 code points? They won't fit in a UInt8Array. There will be too many for async iterators to perform well: you'll hit the promise thrashing issues discussed in the blog post

No, you'll just need to (potentially) keep the last 1-2 bytes of the previous chunk after each iteration. Come on, restartable UTF-8 APIs has been around for more than 30 years.

Re: A better streams API is possible for JavaScript

#86
post #37
post #22

Earlier quoted context omitted.

What was it specifically about the style that stood out as incongruous, or that hindered comprehension? What was it that made you stumble and start paying close attention to the style rather than to the message? I am looking at the two examples, and I can't see anything wrong with them, especially in the context of the article. They both employ the same rhetorical technique of antithesis, a juxtaposition of contrasti…

The problem is less with the style itself and more that it's strongly associated with low-effort content which is going to waste the readers time. It would be nice to be able to give everything the benefit of the doubt, but humans have finite time and LLMs have infinite capacity for producing trite or inaccurate drivel, so readers end up reflexively using LLM tells as a litmus test for (lack of) quality in order to c…

These AI signals will die out soon. The models are overusing actual human writing patterns, the humans are noticing and changing how they write, the models are updated, new patterns emerge, etc, etc. The best signal for the quality of writing will always be the source, even if they are "just" prompting the model. I think we can let one incident slide, but they are on notice.

Re: A better streams API is possible for JavaScript

#87

As it happens i have an even better API than this article proposes! They propose just using an async iterator of UInt8Array. I almost like this idea, but it's not quite all the way there. They propose this: type Stream = { next(): Promise }> } I propose this, which I call a stream iterator! type Stream = { next(): { done, value: T } | Promise } Obviously I'm gonna be biased, but I'm pretty sure my version is also obj…

Other angles of critique & consideration already covered well by sibling commenters. One extra consideration (unrelated to streams, more general) is the API design & dev UX/DX: type Stream = { next(): { done, value: T } | Promise } the above can effectively be discussed as a combination of the following: type Stream = { next(): { done, value: T } } type Stream = { next(): Promise } You've covered the justifications f…

How is it cleaner? I used to actually do that. I wrote everything twice. I even built fancy tools to help me write everything twice.

But the bigger problem here is that sync and async aren't enough. You almost need to write everything three times: sync, async, and async-batched. And that async-batched code is gonna be gnarly and different from the other two copies and writing it in the first place and keeping it in sync is gonna give you headaches.

To see how it played out for me take a look at the difference between:

https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2... https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2... https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2...

Re: A better streams API is possible for JavaScript

#88

Earlier quoted context omitted.

How would you then deal with a stream of UTF8 code points? They won't fit in a UInt8Array. There will be too many for async iterators to perform well: you'll hit the promise thrashing issues discussed in the blog post

No, you'll just need to (potentially) keep the last 1-2 bytes of the previous chunk after each iteration. Come on, restartable UTF-8 APIs has been around for more than 30 years.

But those code points were just inputs to another stream transformation that turns a stream of code points into a stream of graphemes. Rapidly your advice turns into "just do everything in one giant transformation" and that loses the benefits of streams, which are meant to be highly composable to create efficient, multi-step transformation pipelines.

Re: A better streams API is possible for JavaScript

#89
post #79

As it happens i have an even better API than this article proposes! They propose just using an async iterator of UInt8Array. I almost like this idea, but it's not quite all the way there. They propose this: type Stream = { next(): Promise }> } I propose this, which I call a stream iterator! type Stream = { next(): { done, value: T } | Promise } Obviously I'm gonna be biased, but I'm pretty sure my version is also obj…

I did a microbenchmark recently and found that on node 24, awaiting a sync function is about 90 times slower than just calling it. If the function is trivial, which can often be the case. If you go back a few versions, that number goes up to around 105x. I don’t recall now if I tested back to 14. There was an optimization to async handling in 16 that I recall breaking a few tests that depended on nextTick() behavior…

Here is my test harness and results: https://github.com/conartist6/async-perf

Re: A better streams API is possible for JavaScript

#90

Earlier quoted context omitted.

No, you'll just need to (potentially) keep the last 1-2 bytes of the previous chunk after each iteration. Come on, restartable UTF-8 APIs has been around for more than 30 years.

But those code points were just inputs to another stream transformation that turns a stream of code points into a stream of graphemes. Rapidly your advice turns into "just do everything in one giant transformation" and that loses the benefits of streams, which are meant to be highly composable to create efficient, multi-step transformation pipelines.

No, it doesn't turn into this. Those two bytes of leftovers plus a flag are kept inside the stream generator that transforms bytes into code points, every time you pull it those two bytes are used as an initial accumulator in the fold that takes the chunk of bytes and yield chunk of code points and the updated accumulator. You don't need to inline it all into one giant transform.

Come on, it's how (mature libraries of) parser combinators work. The only slightly tricky part here is detecting leftover data in the pipeline.

Post reply on HN