Granted, my practical experience is limited. I haven't really run into any of these kinds of situations outside of database work in Node, and there you've got the major benefit of transactions to do the locking for you, so the async JavaScript code doesn't have to particularly care and can yield whenever it wants.
For me, the real benefit of await / async so far has mostly just been about improving code flow. Promises were already an excellent solution to the async problem, but their syntax for all but the most trivial example is something only a mother could love. async/await makes the code structure suddenly not necessarily look like callback hell, and in a lot of cases it's much more compact and easier to read. It greatly improves the chances that when I come back to it a month later, I won't have to reach back into the past and slap myself for writing that monstrosity. :)
I've done a lot of async/await and I really can't think of any situations where this has been a concern for me. If you're mutating state in method calls without explicitly passing it around, that might be an issue but that's a deeper design issue IMHO.
A mutex really can only exist to serialize async code. If you want it serialized that likely means that code shouldn't be async in the first place.
Mutexes are for situations where flag variables can change state between your instructions to check and set a flag. This can only happen in multithreaded or interrupt driven code.
The reason we don't have a mutex in javascript is that there are better solutions to the problems it solves.
I don't really see how it's a mutex though, you're just returning the same promise to multiple requests. It's far simpler and doesn't use a locking construct.
For example:
``` let release; const acquire = new Promise(resolve => release = resolve);
(async () => {
await acquire;
// use here
release(); // now others can use it
})();
```This is a bit different because multiple people can await the lock here so it's like a "one time multicast mutex" making it more akin to condition variables.
That said - thinking about it as a mutex is a really backwards way in my opinion.
One should think in terms of a dataflow graph, where data-independent nodes can run in any order, or in parallel. One should explicitly think about ordering of effects, and be explicit about effects in general. (Hence the rise of popularity of FP.)
Within the context of the argument you are making, this is disingenuous. There's a big pile of transistors which determine whether it is safe to reorder those instructions.
const foo = async () {
const ice = await freeze(water);
const wCream = await whip(cream);
const base = await pour(liquor, ice);
const cocktail = await put(base, wCream);
return cocktail.serve();
}
In the above fragment, `freeze` and `whip` may run in any order or in parallel, you don't get to choose. Still `pour` never runs before `freeze` (though it can complete before `whip`), and `put` can only run last. This is because the above is syntactic sugar, and the dataflow graph gets encoded in the promises graph, with `.then` clauses giving an unequivocal dependency order where applicable.Same in CPU: two loads can run in either order or in parallel, but an ADD that takes the result of both of them will only run when they both complete.
That example is:
const foo = async () =>
freeze(water)
.then(ice => whip(cream)
.then(wCream => pour(liquor, ice)
.then(base => put(base, cream)
.then(cocktail => cocktail.serve()))));Yes - the benefits of pure functions in particular are increasingly evident.
I thought async/await was just syntactic sugar for promises?
If you were using promises or callbacks it would be the same, except that it won't appear like two sequential lines anymore.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Yes but that doesn’t mean other lines of code can’t be executing while waiting. The waiting isn’t truly blocking the entire application. Plenty of other things can be happening.
On the flip side, there is a Javascript transpiler called nodent which directly converts async functions into the corresponding `Promise.then` calls, with no generators in sight. This transpiler actually generates smaller & faster output than the standard generator-based approaches, since the async/await semantics map more directly to promises.
In other words, async functions really are semantic sugar for promises, even if they have some similarities to coroutines / generators.
To try it yourself, just go to http://nodent.mailed.me.uk/ , check the "spec compliant" option, and look at the generated output.
Although I have found that babel does seem to transpile them differently to Promises
I've definitely needed more locking primitives and ordering but it's mostly for remote code.
There are also a ton of bugs that can happen with memory allocation.
For example if you do 50 async and they all come back and once and try to all allocate memory and then perform some action at the end you can have too many in flight and then use too much memory.
It's not really threaded but you can run into similar problems.
I've done 10+ years of core JVM threading with raw threads and softare transactional memory datastructures and CAS operations and my experience there helps a ton.
Async is definitely not just 'free'
How is this different from normal multithreading?
You can wedge a NodeJS interpreter by not yielding, but you also have control over when that yielding happens.
The same claim can be made for Visual Basic. This always sounds like a stronger guarantee than it actually is. In practice, the exact same mistakes get made irrespective of what the runtime provides.
Ultimately, people have to learn about race conditions in order to write correct code in a nontrivial system. Brushing the 'majority' of such cases under the carpet just makes it harder to educate them.
That said, when you use async/await instead of promises or callbacks, things do change because two sequential lines of code are no longer two truly sequential instructions. You're muddying the most fundamental metaphor of code syntax. That's why I personally don't like async/await syntax, although I get why people want it.