Thing of it as a different analogy. There are various chores to do around the house - some of them must be done synchronously (do the ironing - we don't want to leave the iron on). Some of them can be done asynchronously (run the washing machine). If you want to write out your tasks it might look something like this.
LoadWasher() (sync)
washerRunning = RunWasher() (async)
var cleaning = CleanKitchen() (async)
wetClothes = await washerRunning;
LoadDryer(wetClothes)
clothesToIron = await RunDryer() // while we're awaiting we walk back up the stack and can continue cleaning the kitchen until the dryer is finished
IronClothes(clothesToIron) // this is just happening synchronously
await cleaning //last job to do so we need to make sure we finish!
Don't know if that helps, but I think it's an interesting analogy
That is a good example. It illustrates that async is a partial abstraction on the way to dataflow. It's not immediately obvious from your example what's really going on: whereas re-writing it as dataflow pipelines makes the intent clear:
where `|` is piping the output of one operation into the next one (as per *nix shell). The two lines are independent and so can be scheduled according to resources available.
Doing so makes the inherent dependencies trivially clear. It doesn't matter which are "long" running and which aren't: it's not possible to run the washer until it's been loaded. Using async/await conflates two things:
1. What's "long" running, for some definition of "long" (--> make it async)
So you basically you'd have to rely on each of the function signatures, probably with some text editor/IDE by hovering over them, to figure out which one in the pipe list is Async? Or jump to each of them with ctags.
Then wrap it in a try/catch or pattern match the result to handle the various errors different errors...
> So you basically you'd have to rely on each of the function signatures, probably with some text editor/IDE by hovering over them, to figure out which one in the pipe list is Async? Or jump to each of them with ctags.
No, the point is you don't _need_ async. At least not as a language primitive. Instead, one expresses directly where the data dependencies lie. If an operation in a pipeline blocks, the runtime is at liberty to switch to another task. That already happens today, whether in language runtimes (e.g. Node) or the underlying OS. I don't need to tell the runtime "this operation might block". It already knows.
Instead, we've explicitly defined intent: when `RunWasher()` finishes, `LoadDryer()`. It doesn't matter if the washer takes one micro second or one hour; I can't load the dryer until it's done.
>Then wrap it in a try/catch or pattern match the result to handle the various errors different errors...
True, error handling is missing. But then it's not in the parent example either. The requirements aren't fundamentally different in either case.
Your analogy works as long as there's a machine doing the washing and drying.
If you replace the machines with people, it makes much more sense for the function definition to define what is and isn't synchronous. If you have to do all the chores yourself, just call the functions synchronously. If you have family members you can delegate some work to, you can call the functions asynchronously and wait for them to complete while you're doing your own work (or twiddling your thumbs).
Another problem with await is that you can't run in parallel without going down one abstraction layer. So it just add additional complexity as you need to know two layers. The hardest part with software is to not implement too many features.
My problem with this is if 'I' (or in code, the current program) actually loads the washer and cleans the kitchen, then these things run in serial.
If I do what I've always done in rust, which in use threads, I get lovely easy parallelization. Async only seems useful when your program doesn't do anything non trivial, but just dispatches to other things, as any one function that takes a long time blocks all other async functions until completion?
And so all library functions should be async by default? And if everything is async, then shouldn't synchronous programming require the extra keyword instead of the other way around?
Only if the function "leaks" the async-ness instead of containing it (that is, waiting on any async calls synchronously). But yes, making a function async is a backward-incompatible change, just like manually changing the return type from `T` to `Future<T>`.
Ok let me phrase it differently. If you have some library function that does some IO (for example) and you want to make it as useful as possible, would you declare it async or not? If you make it blocking, then you'd ruin any opportunity to have it called from async functions (because they don't want to be blocked). Therefore, you'd make it async. So that means that all IO library routines will become async, and possibly a lot of others too. Wouldn't it make more sense to have async be the default?
But when all those IO functions are rewritten to be async, their public names can be different. The sync versions needn't change names, so none of the callers would need to change. Nor would the sync versions need to duplicate code, because they can just be thin wrappers that call and wait on their async counterparts. (Note that this wouldn't be possible in JS.)
When people say this, they usually mean "a significant amount of things JavaScript is used for are asynchronous, and IO is virtually always asynchronous," rather than something more literal. The language has asynchronicity deep in its bones, even if it's not entirely made out of it.