Live data from Hacker News

Go-like channels in 10 lines of JavaScript

pedrocattori.dev

21–30 of 65 posts

Re: Go-like channels in 10 lines of JavaScript

#21
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 model either.

So prefer to use channels instead.

Re: Go-like channels in 10 lines of JavaScript

#22
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…

Even better, make use of the built-in promise helpers:

  const manifest = await createAssetsManifest();
  await Promise.all([
    compileBrowser(manifest),
    compileServer(manifest)
  ]);
That makes for such nice, clear code!

Re: Go-like channels in 10 lines of JavaScript

#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 communicate between green threads. But you don't have "Go channels" without select.

Whether that is possible in JavaScript, I don't know. It is possible in a heavy-runtime language to be so locked down that it is either impossible to implement, or impossible to implement with acceptable performance, and I'm not into Node enough to know.

(To be clear, such a thing is not a criticism necessarily. I'm not sure if you could implement select efficiently or correctly from within pure Go, either, if it did not already exist. There's a lot of runtime integration it has that is not exposed any other way. It is s perfectly viable design decision to build a runtime environment that does not give that level of access to the CPU without custom assembly or C or something.)

Re: Go-like channels in 10 lines of JavaScript

#24
post #6

But there is no concurrency in Javascript! So even after this exercise, the running function still has to finish before the next can start. This will only create actual concurrency when each of the actors are themselves doing pretty IO heavy things where they wait for external processes to finish. Otherwise, if you actually have compute heavy processes that should run in parallel you should use the node.js cluster pa…

JavaScript does have concurrency! Try the following: Promise.all([new Promise(t => setTimeout(t, 3000)), new Promise(t => setTimeout(t, 3000))]).then(() => console.log('done')) You'll notice it prints `done` after 3 seconds, not after 6. It just happened to be executed one by one but the VM handles the switching for us. What you're talking about is parallelism, which JavaScript indeed does lack and you'd use cluster…

Is that concurrency? The CPU is not switching between tasks; rather, it's scheduling 2 callbacks to fire after a 3000ms delay.

If you could perform 2 tasks (e.g. console.log(Array(1e8).fill(0).map((a, i) => i)), and have both run to completion without a significant delay between the two tasks, that'd be impressively concurrent.

Re: Go-like channels in 10 lines of JavaScript

#25
I've come to the conclusion that it's simply not possible to fully emulate Go channels in JS/TS. They're too tightly integrated into the rest of the language.

See this talk for some more background (ideally, watch the video). https://go.dev/talks/2012/concurrency.slide#1

Be warned, you will never look at async/await in the same way again.

Re: Go-like channels in 10 lines of JavaScript

#27

This is interesting. Correct me if I don't understand is this only concurrent with respect to IO is that right? If you run a IO operation or network call (fetch api) in those promises, those shall be concurrent with the CPU execution of your Javascript? I think async/await is a great primitive. I've been working on implementing a async/await switch statement based state machine* in Java for multithreaded async/await.…

Generators are, IMO, a much better primitive: they’re a strict superset of the functionality of async/await and they allow a bunch of other sorts of asynchronous control structures.

Re: Go-like channels in 10 lines of JavaScript

#28
You might as well avoid wrapping the types and just split the writer and reader: it's good practice to do this anyway, since you probably only need one half of the oneshot channel on each side and having a handle to something that can both read and write seems like a logic error. You can also augment the writer function to throw into the promise, if required!

  type Sender = ((e: null, v: T) => void) & ((e: E) => void);

  function oneshot(): [Promise, Sender] {
    let send: Sender;
    const recv = new Promise((resolve, reject) => {
        send = (err, v?: T) => err == null ? resolve(v!) : reject(err);
    });
    return [recv, send!];
  }

Re: Go-like channels in 10 lines of JavaScript

#29
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 which puts you in the same place as before + extra overhead from a pair of extra promises.

Re: Go-like channels in 10 lines of JavaScript

#30
post #6

But there is no concurrency in Javascript! So even after this exercise, the running function still has to finish before the next can start. This will only create actual concurrency when each of the actors are themselves doing pretty IO heavy things where they wait for external processes to finish. Otherwise, if you actually have compute heavy processes that should run in parallel you should use the node.js cluster pa…

> 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.
Post reply on HN