Asynchronous execution isn't an unfortunate design choice to be papered over; better to make it as easy as possible to learn and work with.
Asynchronous execution isn't an unfortunate design choice to be papered over; better to make it as easy as possible to learn and work with.
The Linux (and other OS'es) kernel handles I/O asynchronously, yet processes using open(2) + friends work quite well and are usually written in synchronous style. That the actual asyncness is hidden in kernel space does in general not lead to bad programs; and whoever needs async in the space of a single process can still use async tools provided by the kernel when needed. That tooling is much better as it leads to simpler code.
I don't see ES7's async support being there yet. Elixir, Go, a number of others: much more.
I personally prefer the async solution, and I think async/await is a good solution to make async code a little more readable.
What do you mean the leaky blocking IO. It is the non-blocking IO that is leaky, isn't it. If at the business logic there is a request being processed that needs to go through steps a,b,c and then return. b, can't be done unless a finishes and c unless b finishes is mapped pretty cleanly to an execution context of a thread/actor/goroutine/task/process.
Shoving IO callback functions in between handling the steps, or spreading them across callbacks or using promises is leaking the abstraction of the platform not being able to handle concurrent processing very well.
I agree that if you have some pure linear steps (do A, then do B, then do C, and probably use the result from the last step for the next), then the synchronous abstractions work fine. But as soon as you add various timeouts, different error handling strategies, multicast communication, cancellation and other stuff to your async IO procesing then it isn't purely linear anyway. I often end up implementing quite complex state machines for heavy IO related code, and for this I am more happy with working in an asynchronous single-threaded environment.
> for heavy IO related code, and for this I am more happy with working in an asynchronous single-threaded environment.
You're not describing the majority of applications. The vast majority of applications do negligable amounts of complex network I/O (ignoring GUI, which is handled for them by... an abstraction).
It would be a bad idea to base our abstractions on things only needed for a tiny minority of applications.
When I worked on simulation code some years ago I haven't thought about complex IO at all. Even when I did some basic web development (with PHP...) 16 years ago I had nothing but synchronous IO and was happy. Now realtime gateway systems are my daily business and the "typical application" looks completly different.
And you already have given GUI as a counter example yourself. More then a tiny majority of applications have a GUI. And all major GUI frameworks are built on top of asynchronous abstractions, because you want GUIs to be reactive (no hanging due to blocking stuff), and because every input port (either from a human input device or from network) can change the shared state and cause output to more than one output device.
I'm specifically going against my personal bias. I write server-side applications, but most of the code out there is basically apps and such.
> And you already have given GUI as a counter example yourself. More then a tiny majority of applications have a GUI. And all major GUI frameworks are built on top of asynchronous abstractions, because you want GUIs to be reactive (no hanging due to blocking stuff), and because every input port (either from a human input device or from network) can change the shared state and cause output to more than one output device.
If you look at how the users of GUI frameworks write their code, it's mostly synchronous code with some interspersed message-passing type code. The asynchronous nature of GUIs is only really relevant when dealing with button presses and such.
You don't want to have to program all of it as if it's asynchronous. Having to reify the stack manually into a state machine would quickly induce madness for all but the most trivial of GUIs.
Reactive GUIs are pretty simple to do using blocking code: Have a main thread resonsible for the GUI, offload long-lasting work to other threads. Use message passing for communication. Done. You're still programming in a mostly-synchronous model. I think my point stands.
EDIT: Btw, the beauty of this message-passing-mostly-synchronous model is that it doesn't force you to completely change everything in your program around the asynchronous bits. You restrict your asynchrony to where it really matters. That's a big deal for clarity, IMO.
Creating a socket() creates a blocking socket by default, doesn't it. Doing a recv on it, blocks the current thread. Pretty sure by default IO is synchronous. Sure at some point they are all connected to a single wire plugged into the switch and there is a single network card. But if you are thinking that low, then well javascript is not the framework to talk about we are in firmware, dpdk, and driver land.
The socket API is synchronous. Do do stuff "asynchronously" you have to go the extra mile and enable turn on some options, use select/poll/epoll callbacks, explicitly pass state around between callbacks etc.
> What blocking does is adding an extra step (start the process AND waiting for the result) and thereby hiding something.
See to me at the socket layer non-blocking is the extra step. Having to enable a new mode, then use some extra polling hub to see which socket has data etc. That mixes context between separate requests, etc.
> I often end up implementing quite complex state machines for heavy IO related code
In specializes cases for performance reasons I think switching to asynchronous, but that is a rather special case. It works for short callback chains and situations optimized for IO throughput say things like nginx or haproxy. Doing it for an online store or some business middle layer would be very tricky.
Look at the LWIP library for embedded for example. There the async API is the default one, and you have to take the extra mile (and some performance hit) to get the blocking behavior. The midori OS prototype also seems to have implemented a pure async IO subsystem.
I also think it depends on the use-case whether one or the other model is more appealing, but I don't think it's only for performance reasons. A lot of people here think about IO mainly in terms of http request processing because web services are the usual domain here. For this such applications I think blocking abstractions come quite naturally, because you there you read the input stream once, then do a sequence of intermediate processing steps and then write a response. If you instead are working on other domains (writing a message broker, a gateway application, http/2 library, realtime sensordata processing system, ...) where there is no linear sequence of read/write operations and where you have shared state between your IO endpoints then your needs and the preferred building blocks might be different.
If you want that in JavaScript, you can have it; promises were created on the assumption that asynchronous logic is desirable as a matter of course.
In Javascript I would probably do... the same thing? But I would do it using promises?
// using Promise.race so whoever finishes first wins
var fetchingWithTimeout = Promise.race([
// first promise: actually try to perform the computation
fetching,
// second promise: wait x seconds, then reject
Promise.delay(x*1000).then(Promise.reject('Timed out')),
]);
You can apply the timeout wherever you want (or apply different timeouts in different corners of the app) without throwing out the raw promise to perform the computation no matter the time cost.In cases like a node.js server, Promises allow a single node instance to handle thousands of concurrent requests because the event loop isn't blocking waiting for a single IO request to complete, the process can happily do lots of other things while Promises are pending.
So it's right that promises allow me to do the same thing I would do with sequential code in other languages? If I had the option of using coroutines when would I choose to use promises?
Edit: I'm asking because the context of this thread is that one person said that sequential APIs for asynchronous operations, such as open(2), are pretty nice, and someone else said no they're not pretty nice and we should explicitly deal with the asynchronous nature of operations like open(2) by NOT writing sequential code there.
If you don't have either, then you'll probably end up re-implementing one or the other eventually anyway.