Live data from Hacker News

Go-like channels in 10 lines of JavaScript

pedrocattori.dev

41–50 of 65 posts

Re: Go-like channels in 10 lines of JavaScript

#42
post #9

Earlier quoted context omitted.

I actually think async/await is a terrible primitive, and if you’re on Java, the approach of Project Loom is vastly superior (i.e. threads can be cheap so everything can just be synchronous again). I’ve no problem with library-level concepts like promises and futures but it feels very short sighted to put it into an actual language, it’s just noise.

Why don't you like async/await? 8 years ago I wrote a npm package that turned code that looks like this: tcp.send("syn", function(syn) { console.log("received", syn); tcp.send("syn-ack", function(synack) { console.log("received", synack); tcp.send("ack", function(ack) { console.log("received", ack); }); }); }); into this: seq([tcp, console], function(tcp, console) { var syn = {}; var synack = {}; var ack = {}; tcp.se…

I think async/await is just noise: if you already have a heavy runtime like JavaScript or Python does (I forgive Rust for this one because it's so tied to reducing runtime overhead), you might as well handwave the distinction between green threads and system threads away and just pretend all asynchronous calls are synchronous (e.g. like how the eventlet or the Go runtime do it): it's painful when you have to call an asynchronous function from a synchronous context!

If you put while (true) { } in an asynchronous function, wouldn't you also have a resource starvation problem? For what it's worth though, Loom threads are planned to be fully preemptible.

Re: Go-like channels in 10 lines of JavaScript

#43

It's a bit out of fashion, but same thing can be achieved without any extra code using a built-in EventEmitter: let channel = new EventEmitter() await Promise.all([ compile.browser(channel), compile.server(channel) ]) // in compile.browser channel.emit("manifest", assetsManifest) // in compile.server channel.once("manifest", (assetsManifest) => ... ) A new emitter is used each time, so the end result is the same. Cur…

EventEmitter doesn’t hold on to its values, so if the `channel.once` listener doesn’t get attached before `emit` is called, the value will be missed. Also, in order to wait on an event, you usually end up with a promise anyway (so `await` can be used).

Re: Go-like channels in 10 lines of JavaScript

#44
post #19

There are a ton of other approaches that would work with NodeJS that would be even more simple though right? The easiest or first that comes to mind is just have your function that does whatever functionality is required for creating this assetsManifest (whatever both compileServer and compileBrowser depend on). Lets call this createAssetsManifest. Then you have your 2 compile async functions but you just don't await…

The manifest is an artifact produced by the browser compilation, but it can be shared _before_ the browser compilation writes its results to disk. I _could_ refactor to have "browser compilation phase 1", then assets manifest, and then "browser compilation phase 2", but that's not how I model it in my head. Plus it would mean a diverging interface for `compileBrowser` and `compileServer` which doesn't fit my mental m…

> but that's not how I model it in my head.

I think this is the central matter when it comes to primitives for asynchronous programming.

There exist many ways we can think about async tasks. The JavaScript ecosystem provides for multiple. i.e., event callbacks, async/await, generators, and more.

Programmer reach for tools which best match how we have learned to model these problems in our heads.

It's okay for people to use what works best for them.

Re: Go-like channels in 10 lines of JavaScript

#45

Earlier quoted context omitted.

> But there is no concurrency in Javascript! Yes and no. At the “ECMAScript”-level, JavaScript has no built-in concurrency, but every serious JavaScript implementation enables proper concurrency via the async primitives and the internal scheduling. Put simply, if you know how to write async code properly, your JavaScript code can achieve high concurrency!

No. All the JS code is still executing serially in the same event queue. This is a good thing, lean into the guarantees it provides.

In an async function, the only guarantee you have is that code between two await points is not interruptible. As you have more and more async operations, this guarantee gets pretty weak (if you have more than one pending async task and the current task yields, which one will run next?) and you have to start employing the usual locking mechanisms to ensure correctness.

Re: Go-like channels in 10 lines of JavaScript

#46
post #23

The real essence of Go channels is their ability to participate in the built-in select keyword. Of course, Go does not have access to any magic CPU instructions that make it something Go can uniquely do. But anything that wants to be "Go channels but in X" need to be implementing select, not a send operation. Send operations are easy. And a useful primitive! Way back in the day, Queue in Python was the way to communi…

Disclaimer: I am the author of this library [1].

A few days ago, I ported ocaml/Event [2] to JavaScript, which provides concurrent ML-style synchronization operations.

It is possible to implement `Channel` and `select` in JS, but it is not easy to provide an idiomatic API and integrate it with the Promise ecosystem.

[1]: https://github.com/dhcmrlchtdj/sync-op [2]: https://ocaml.org/api/Event.html

Re: Go-like channels in 10 lines of JavaScript

#47

This needs a comparison with streams, as opposed to promises. Streams are what you would use to achieve this in Node.js land. https://nodejs.org/api/stream.html

Well, the context is Node.js, and I would definitely use promises over streams for what’s described in the article (albeit more directly). Node streams are complicated with a lot of historical baggage, and massive overkill for waiting on a single value produced by another operation.

Re: Go-like channels in 10 lines of JavaScript

#48
post #10
post #8

Earlier quoted context omitted.

Its a Typescript-ism for asserting that the value is not `undefined`. So you're telling the typechecker to trust you on this one

Thank you, didn’t know about this one!

That is (was) a good thing! It’s basically the `as any` of strict null checks, and just as unsafe.

Now that you do know about it, please use it sparingly if at all, i.e. when you’re absolutely sure you know more than the type checker, or when you’re in a context where it’ll be caught by other means. My typical lint setup disallows it in source code without an explanatory comment, and allows it in tests under the assumptions that either they’ll fail if wrong or that a reviewer will call out the test as overly complicated.

Re: Go-like channels in 10 lines of JavaScript

#49
This is a case where a function is conceptually producing two promises: one for an intermediate result and one for its final result.

I would attack this one of two ways, both of which I feel are more idiomatic than trying to emulate Go in JS:

1) Factor out the intermediate computation and promise:

    const manifestPromise = buildManifest();
    await Promise.all([
      compileBrowser({manifestPromise});
      compileServer({manifestPromise});
    ]);
2) Just return the two promises from compilerBrowser():

    function compilerBrowser() {
      let resolveManifest;
      const manifest = new Promise((res) => resolveManifest = res);

      const result = (async () => {
        // Compute manifest and resolve it before the final result:
        resolveManifest(manifest);

        // Compute the result of the result...
        return finalResult;
      }());

      return {
        manifest,
        result;
      };
    }
IOW, decompose things into smaller pieces (1) and/or compose them into values that match what you need (2) and remember that a function can return a group of promises instead of a single one.

Re: Go-like channels in 10 lines of JavaScript

#50

Hmm, the way I see it is that this only makes a difference if your "more work" is async, in which case I would consider if the function needed refactoring instead - not possible to say with toy code of course. Something like: await makeManifest().then(manifest => Promise.all([moreWork(manifest), compileServer(manifest)])) If, however, "more work" is synchronous then the promise would not resolve until the next tick w…

Yep, totally could do that. But I like I mentioned here (https://news.ycombinator.com/item?id=34659597) I wanted to keep the interfaces for `compileBrowser` and `compilerServer` since my mental model for it is that those two are "siblings".
Post reply on HN