function transfer(amount, acct1, acct2) {
var current_balance = await acc1.balance()
if current_balance > amount {
await acct1.sub(amount)
await acct2.add(amount)
}
}
Now what happens if you get multiple calls to transfer? What you want is probably something like: function transfer(amount, acct1, acct2) {
await acct1.Lock()
....
await acct1.UnLock()
}If anything getting this right on the front-end is often extra problematic because of the high cost of round-tripping state to the server.
It's dangerous to assume that just because JS has a single main thread that you don't need to think about sequencing of operations and locking.
> All this async code without decent locking primitives is leading to a rabbit hole of race conditions...
No, it's not. Here's a lock:
let lock = Promise.resolve()
// ...
lock = lock.then(() => { /* critical section */ })
For the rare instances where more complex async flow control is required, there are a bunch of libraries for that (e.g. async[0], p-queue[1], etc.).To lay out one example as clearly as I can: suppose you had two calls to transfer with the same args.
* The 2nd call to `await acc1.balance()` was very quick beating the 1st call
* The `current_balance` is greater than `amount` and so it runs `await acct1.sub(amount)`
* Before that can finish, the 1st call to `balance()` returns with the old balance before subtraction begins.
* Now another transfer is initiated with the incorrect amount.
If `amount` is larger than `current balance` you've got an issue on your hand.
I believe one could implement a mechanism around this using a set of booleans/ints for each account and managing the call to `transfer` with each one of those. But that's the point - we could have primitives to do that.
By the way, there is a particular issue with this specific example. The `transfer` function signature must be marked as `async`. You would need to add this to make it run properly.
Can you elaborate a bit more on this? I'm unsure about how locking in a single-threaded environment would work. And how would it really differ from async/await or promises which can handle race conditions already?
One area where the lack-of locking primitives does worry me is the use of shared mutable memory. I've never used these techniques in JS, but if I remember right one can now do something like this:
1. Create a shared memory array
2. Spawn several workers which do work on shared arrays
3. Pass a reference to the array to each worker and let them run.
This seems like an area where we could start to see these problems start appearing. There's a module related to atomic operations [1] but there is no language-level enforcement of using it. There could be good reasons why this isn't worrying but I just don't happen to know that off hand.
[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...