Live data from Hacker News

A better streams API is possible for JavaScript

blog.cloudflare.com

71–80 of 167 posts

Re: A better streams API is possible for JavaScript

#71

Earlier quoted context omitted.

JS engines actually are optimized to make that usage pattern fast. Small, short-lived objects with known key ordering (monomorphism) are not a major cost in JS because the GC design is generational. The smallest, youngest generation of objects can be quickly collected with an incremental GC because the perf assumption is that most of the items in the youngest generation will be garbage. This allows collection to be o…

What happens when I send an extremely high throughput of data and the scheduler decides to pause garbage collection due to there being too many interrupts to my process sending network events? (a common way network data is handed off to an application in many linux distros) Are there any concerns that the extra array overhead will make the application even more vulnerable to out of memory errors while it holds off on…

Yeah I don't think that's generally a problem for JS engines because of the incremental garbage collector.

If you make all your memory usage patterns possible for the incremental collector to collect, you won't experience noticeable hangups because the incremental collector doesn't stop the world. This was already pretty important for JS since full collections would (do) show up as hiccups in the responsiveness of the UI.

Re: A better streams API is possible for JavaScript

#72

The Observables spec should just get merged and implemented. https://github.com/tc39/proposal-observable

Observables has moved to WHATWG [1] and been implemented in Chrome, although I don't know if the other browsers have expressed any interest (and there's still some issues [2] to be worked through).

But Observables really do not solve the problems being talked about in this post.

[1] https://github.com/WICG/observable [2] https://github.com/WICG/observable/issues/216

Re: A better streams API is possible for JavaScript

#73

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…

> 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.

Re: A better streams API is possible for JavaScript

#75

There's a lot I like about this API, mainly the pull-based iterator approach. I don't really see what the value of the sync APIs are though. What's the difference of just using iterators directly for sync streams?

It avoids the overhead of Promises, so I can imagine that this would be quite useful if you know that blocking the thread is fine for a little while (e.g. in a worker).

Re: A better streams API is possible for JavaScript

#76

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 for the 2nd signature, but it's a messy API. Specifically:

> My way, if I define a sync transform over a sync input, the whole iteration can be sync making it possible to get and use the result in sync functions. This is huge as otherwise you have to write all the code twice: once with sync iterator and for loops and once with async iterators and for await loops.

Writing all the code twice is cleaner in every implementation scenario I can envisage. It's very rare I want generalised flexibility on an API call - that leads to a lot of confusion & ambiguity when reading/reviewing code, & also when adding to/editing code. Any repetitiveness in handling both use-cases (separately) can easily be handled with well thought-out composition.

Re: A better streams API is possible for JavaScript

#77

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…

I liked conartist6's proposal,

  type Stream = {
    next(): { done, value: T } | Promise
  }
Where T=Uint8Array. Sync where possible, async where not.

Engineers had a collective freak out panic back in 2013 over Do not unleash Zalgo, a worry about using callbacks with different activation patterns. Theres wisdom there, for callbacks especially; it's confusing if sometime the callback fires right away, sometimes is in fact async. https://blog.izs.me/2013/08/designing-apis-for-asynchrony/

And this sort of narrow specific control has been with us since. It's generally not cool to use MaybeAsync = T | Promise, for similar "it's better to be uniform" reasons. We've been so afraid of Zalgo for so long now.

That fear just seems so overblown and it feels like it hurts us so much that we can't do nice fast things. And go async when we need to.

Regarding the pulling multiple, it really depends doesn't it? It wouldn't be hard to make a utility function that lets you pull as many as you want queueing deferrables, allowing one at a time to flow. But I suspect at least some stream sources would be just fine yielding multiple results without waiting. They can internally wait for the previous promise, use that as a cursor.

I wasn't aware that generators were far too slow. It feels like we are using the main bit of the generator interface here, which is good enough.

Re: A better streams API is possible for JavaScript

#78

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.next(value);
  });

Re: A better streams API is possible for JavaScript

#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 that stopped happening, such that the setup and execution steps started firing in the wrong order, due to a mock returning a number instead of a Promise.

I wonder if I still have that code somewhere…

Re: A better streams API is possible for JavaScript

#80

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 think the context that some other responders are missing is that in some functional languages, like Elixir, streams and iterators are used idiomatically to do staged transforms of data without necessitating accumulation at each step.

They are those languages versions of goroutines, and JavaScript doesn’t have one. Generators sort of, but people don’t use them much, and they don’t compose them with each other.

So if we are going to fix Streams, an implementation that is tuned only for IO-bound workflows at the expense of transform workflows would be a lost opportunity.

Post reply on HN