Hacker News new | ask | show | jobs
by samsquire 1233 days ago
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. I want to handle the scheduling of multiple promises eagerly, so when you call async task1(); async task2(); async task3(); it schedules them all independently on different threads.

* if you've used protothreads in C, this is what it is similar to.

To do interleaved concurrency in Javascript, you could do the same pattern (switch based state machine) or do my concurrent looping approach. You break up your long CPU task into lots of microtasks and switch between them concurrently with a switch statement. You switch between tasks with a scheduler loop, so you do a bit of work on each task independently. It's concurrent but not parallel.

here's my writeup of concurrent loops which takes arbitrarily nested loops and turns them into an iterator https://github.com/samsquire/ideas4#133-concurrent-loops---l...

here's my java state machine of multithreaded async/await in Java, that could be adapted to Javascript except it wouldn't be parallel.

https://github.com/samsquire/multiversion-concurrency-contro...

3 comments

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.send("syn", this(syn));
    console.log("received %s", syn);
    tcp.send("syn-ack", this(synack));
    console.log("received %s", synack); 
    tcp.send("ack", this(ack));
    console.log("received %s", ack); 
  });
it was never ready for production use, but it showed that synchronous code illusion can be created from callbacks.

How would you prefer to write asynchronous code?

How does Loom handle resource starvation problem? If a thread puts a while (true) {} in there somewhere, can that thread be preempted away from that kernel thread?

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.

Sounds like we’re agreeing - nobody _wants_ to write async code cos it’s terrible. You didn’t quite perfect the syntax above but I’m not denying it’s possible - Clojure treats async as a library, not a fundamental language feature, for example. And now with Loom the implementation is even simpler. People want to write synchronous code that can run in parallel without having to do something weird. History will hopefully prove that this is best solved as an implementation detail, not as part of a language’s semantics.
Hear, hear

The worst part of Rust, too, is that async/await is tacked on— and it’s just a bad primitive to start with.

I have many more thoughts on this that I might like to blog about, but it can be improved by having a async runtime of some kind (we don’t call malloc a runtime, but it is— and you need something similar for ergonomic async) — to wit, the de facto pseudo-answer for this in Rust is tokio (which should just be promoted to the stdlib, at this point; the vast majority of async code depends on it’s particularities).

It’s also causes quote-unquote “function coloring problems” left, right, and center — which is also avoidable. Just for kicks, consider a language with “call-async”/“yield”. Tie that in with a runtime and this whole topic becomes much, much, cleaner. Async at the callsite! (This is the blog I’ve been meaning to write)

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.
go channels aren't threads but means to pass messages between running goroutines (also not threads, but are boxed like one). Go has first-class async built-in using `go func(){ return long_running_thing(); }()` to spawn a coroutine that executes long_running_thing(). The problem with go is you can either lock with a mutex (bad for google scale) or you can do like Erlang do and pass messages. Ok, but how? Channels. Go has support for a special keyword called `chan` that when used with `make` it will allocate a channel of a defined type. Essentially a FIFO queue to grok it in your head. Where you pass in your object of defined type, and the other side reads it in a blocking select to broker the message. Select in Go can block on a number of channels so it's easy to listen for events in this way. SIGTERM, Messages, TCP Data, etc.