4 ms·
In javascript anywhere you see await, you've introduced an explicit scheduling point. There are cases where even in a single threaded env you want to "wait" unt
by shanemhansen 7y ago
In javascript anywhere you see await, you've introduced an explicit scheduling point. There are cases where even in a single threaded env you want to "wait" until some other async process is complete. To use one of the old school examples:
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()
}
- deleted 7y ago[deleted]
- pseudoramble 7y agoGood point! I hadn't thought of it this way since it often doesn't come up as often in my experiences with front end stuff. 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.
- hombre_fatal 7y agoI don't think it really makes sense to essentially say "Javascript is rife with race conditions like any other language" just because yes, you still need to use transactions when using a database.
- vidarh 7y agoYou're reading too much into the example. Your 'database' can be your frontend js state. There is nothing in the example that limits the problem to backend development.
- jeswin 7y agoYeah but the impact of race conditions on front-end code (which is always single user) is minimal; unlike the backend where it's catastrophic and not fixable by refreshing the page.
- vidarh 7y agoIt is fixable by refreshing the page if there are no race conditions in the front-end. 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.
- olalonde 7y agoIn that case, the code would be sync and there wouldn't be a "race condition".
- vidarh 7y agoI just the other day had to step in to deal with a race condition in a frontend caused by improperly handling async API requests. 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.
- olalonde 7y agoWhile writing async code can indeed be challenging, I just disagree with the idea that JavaScript is lacking good "locking primitives": > 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.). [0] https://caolan.github.io/async/v3/docs.html https://caolan.github.io/async/v3/docs.html [1] https://github.com/sindresorhus/p-queue https://github.com/sindresorhus/p-queue
- blacksoil 7y agoGood example. I think this should be handled by whatever data store is being used, though. In case of SQL, transaction.
- asoo 7y agoYou'd need to lock both accounts... but even then. Doing any kind of accounting operations without being atomic, transactional or idempotent is just wrong. Even with single call - acct2.add() could fail and you're left with broken state. If this is in-memory state then you don't need asyncs at all. If it's not, Locks just give you false sense of safety.