News for hackers. Stuff that matters.
Re:V8 adds support for top-level await by londons_explore on Tuesday September 24, 2019 @06:20PM (#21065236)
All this async code without decent locking primitives is leading to a rabbit hole of race conditions...

It doesn't matter that it's all single threaded if all your function calls may or may not block and run a bunch of other code in the meantime, mutating all kinds of state.

I feel like JavaScript developers of the 2020's are going to relearn the same things the C programmers of the 1990's learn, just a few levels of abstraction higher.

Re:V8 adds support for top-level await by _bxg1 on Tuesday September 24, 2019 @07:17PM (#21065728)
Data races are impossible in JavaScript because it's single-threaded. Race conditions are possible in any language that can do anything asynchronous, which is basically all of them. But the general benefit you get from JS being single-threaded is the fact that any given callback is transactional. No other code will ever come in and mutate state between two regular lines of JavaScript code. Achieving this is pretty much the whole point of locking mechanisms, so under normal circumstances they aren't necessary for JS.

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.

Re:V8 adds support for top-level await by zeta0134 on Wednesday September 25, 2019 @03:02AM (#21068420)
Ehh, I get where you're coming from, but await is at least explicit about it. Did you promise.then()? Did you call this function by putting an await in front of it? You yielded on purpose. If you need to keep hold of a resource on either side of that explicit action, either don't use either of those constructs, do but write a locking mechanism, or stop and think long and hard about why the lock is necessary.

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. :)

Re:V8 adds support for top-level await by gregmac on Tuesday September 24, 2019 @07:47PM (#21065995)
> two sequential lines of code are no longer two truly sequential instructions

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.

Re:V8 adds support for top-level await by PetahNZ on Tuesday September 24, 2019 @07:54PM (#21066055)
One use case I often find is a caching layer between JS and a HTTP call. If there are 2 calls to a non cached endpoint, the HTTP will fire off twice before caching the result. You can work around this by returning the first promise from the cache, but this is essentially a mutex. Having locking primitives would solve this and require less boilerplate code.
Re:V8 adds support for top-level await by tiglionabbit on Tuesday September 24, 2019 @11:46PM (#21067417)
Caching the promise is a perfectly good solution. Locks aren't needed.
Re:V8 adds support for top-level await by kbenson on Tuesday September 24, 2019 @08:31PM (#21066310)
Seems like you should be able to build a lock mechanism fairly simply with async/await, but I agree, it would be nice if it was a built in primitive. It does feel a little silly to implement a lock function when the engine much be using one to support its async functionality in the first place, so you're introducing a lot of inefficiency.
Re:V8 adds support for top-level await by Arelius on Tuesday September 24, 2019 @10:21PM (#21066952)
I don't think it's true that the engine must be using a mutex to support it's async functionality. Or that it is true that an OS mutex will be any more efficient than alternative solutions.

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.

Re:V8 adds support for top-level await by kbenson on Wednesday September 25, 2019 @01:01AM (#21067744)
You're right that it may not be required for the async functionality (especially since lock-free schedulers exist, and the engine is basically a single processor scheduler for processes). I do think it's likely in use other places though, just because it's much easier to write correct code with one.
Re:V8 adds support for top-level await by matharmin on Wednesday September 25, 2019 @02:53AM (#21068386)
Implementing a mutex is very simple and efficient in JavaScript - probably much lower overhead than the Promises itself.
Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @11:17PM (#21067271)
You write this boilerplate to avoid one extra non-cached HTTP call on a (browser?) client? Am I understanding correctly?
Re:V8 adds support for top-level await by londons_explore on Wednesday September 25, 2019 @03:37AM (#21068631)
One extra call could be a multi-megabyte video chunk, or the code flow might mean this case always occurs, potentially hundreds of times before the promise resolves.
Re:V8 adds support for top-level await by matharmin on Wednesday September 25, 2019 @02:56AM (#21068395)
It's really not a lot of boilerplate - once you have the small utility function, it can be one additional line of code.
Re:V8 adds support for top-level await by random314 on Wednesday September 25, 2019 @11:56AM (#21071993)
When your program is single threaded, a simple boolean flag variable can act as a mutex, you don't need a mutex for this.

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.

Re:V8 adds support for top-level await by Arelius on Tuesday September 24, 2019 @10:18PM (#21066936)
Do you really want to block all of your http calls on the result of the prior call in the off-chance that they might share a result just so you can only cache the results and not the promises?

The reason we don't have a mutex in javascript is that there are better solutions to the problems it solves.

Re:V8 adds support for top-level await by hombre_fatal on Tuesday September 24, 2019 @11:03PM (#21067196)
Maybe I don't understand your response, but a map of promises to handle parallel in-flight requests for the same resource (which the grandparent pitched) is basically the most elegant yet simple solution to this common problem.

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.

Re:V8 adds support for top-level await by inglor on Wednesday September 25, 2019 @02:47AM (#21068349)
A promise is a lock in that resolving it is the 'release' and awaiting it is `acquire`.

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.

Re:V8 adds support for top-level await by nine_k on Tuesday September 24, 2019 @08:28PM (#21066285)
I think it's worth stopping to think in terms of "sequential lines of code". Even at CPU level, "sequential" instructions aren't, for a decade or so, to say nothing of xplicitly async code.

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.)

Re:V8 adds support for top-level await by cellularmitosis on Tuesday September 24, 2019 @11:17PM (#21067272)
> Even at CPU level, "sequential" instructions aren't

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.

Re:V8 adds support for top-level await by nine_k on Wednesday September 25, 2019 @12:20AM (#21067566)
Thar same pile of transistors, with a thick layer of software, determines whether it's safe to proceed a coroutine.

    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.

Re:V8 adds support for top-level await by esprehn on Wednesday September 25, 2019 @12:45AM (#21067676)
Is there a bug in your example? You await freeze before calling whip. They can't run in any order or in parallel and must complete sequentially. pour also cannot complete before whip since whip is being awaited.

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()))));
Re:V8 adds support for top-level await by nine_k on Wednesday September 25, 2019 @10:29AM (#21071106)
Thanks. I stand corrected.
Re:V8 adds support for top-level await by derangedHorse on Wednesday September 25, 2019 @08:57AM (#21070278)
Maybe there’s a misunderstanding with how async/await works...
Re:V8 adds support for top-level await by nine_k on Wednesday September 25, 2019 @10:31AM (#21071128)
Apparently so — thank you!
Re:V8 adds support for top-level await by chrisweekly on Tuesday September 24, 2019 @08:59PM (#21066493)
>"be explicit about effects in general. (Hence the rise of popularity of FP.)"

Yes - the benefits of pure functions in particular are increasingly evident.

Re:V8 adds support for top-level await by ihuman on Tuesday September 24, 2019 @08:32PM (#21066321)
> 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

I thought async/await was just syntactic sugar for promises?

Re:V8 adds support for top-level await by matharmin on Wednesday September 25, 2019 @02:59AM (#21068403)
Yes. I think what is meant here, is that two sequential lines with `await` in them are no longer sequential instructions, and other code may execute in between them.

If you were using promises or callbacks it would be the same, except that it won't appear like two sequential lines anymore.

Re:V8 adds support for top-level await by _bxg1 on Wednesday September 25, 2019 @12:49AM (#21067696)
It is; but in my experience that fact can make it harder to see what's truly going on.
Re:V8 adds support for top-level await by h8hawk on Tuesday September 24, 2019 @09:56PM (#21066815)
No it's not. async/await's semantic is similar to coroutines, and is implemented by them.
Re:V8 adds support for top-level await by ihuman on Tuesday September 24, 2019 @10:57PM (#21067162)
Are you sure? From what I can find, async [0] causes a function to a return a promise, which then returns the result. Await [1] takes a promise, and waits for it to either be resolved or rejected.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re:V8 adds support for top-level await by matt-attack on Tuesday September 24, 2019 @11:46PM (#21067414)
> and waits for it

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.

Re:V8 adds support for top-level await by nemetroid on Wednesday September 25, 2019 @07:33AM (#21069681)
Sure, but the next line of the function awaiting the result can't execute while waiting.
Re:V8 adds support for top-level await by h8hawk on Wednesday September 25, 2019 @12:35AM (#21067639)
from your first link: "... , async/await is similar to combining generators and promises."
Re:V8 adds support for top-level await by s_tec on Wednesday September 25, 2019 @02:02AM (#21068093)
It is true that async functions work similarly to the coroutines found in other languages, and that some transpilers convert async functions into Javascript generator functions under the hood. However, these are both implementation details.

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.

Re:V8 adds support for top-level await by gitgud on Wednesday September 25, 2019 @01:16AM (#21067836)
Maybe provide some evidence of this, I've also understood them as syntactic sugar for Promises.

Although I have found that babel does seem to transpile them differently to Promises

Re:V8 adds support for top-level await by burtonator on Tuesday September 24, 2019 @08:13PM (#21066179)
You're sort of right but it depends on the races and the data structures. If you depend on a remote function and ordering is important lots of problems can happen.

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'

Re:V8 adds support for top-level await by tudorconstantin on Tuesday September 24, 2019 @10:53PM (#21067138)
Since an async function is simply a function that returns a promise is there actually any difference between using async/await and using promises explicitly?
Re:V8 adds support for top-level await by dboreham on Tuesday September 24, 2019 @11:07PM (#21067219)
It takes longer to boil your brain.
Re:V8 adds support for top-level await by jinglebells on Wednesday September 25, 2019 @06:18AM (#21069332)
If you're compiling to ES2016 or lower, it will turn the promises into state machines.
Re:V8 adds support for top-level await by eurasiantiger on Wednesday September 25, 2019 @02:51AM (#21068371)
Terseness and readability towards less asynchronously minded folk
Re:V8 adds support for top-level await by BigJono on Tuesday September 24, 2019 @10:57PM (#21067170)
None, as far as I know
Re:V8 adds support for top-level await by gregors on Wednesday September 25, 2019 @10:05AM (#21070871)
"logical" race conditions can still occur. Eg. 3 lines of code should run without interrupt. The inclusion of an async operation within this block now causes context switch (execution switch). I've had to debug this tricky situation in external libs before.
Re:V8 adds support for top-level await by derangedHorse on Wednesday September 25, 2019 @08:50AM (#21070236)
I don’t understand how the async/await syntax changes anything. It’s really just syntactic sugar used to manipulate promises isn’t it?
Re:V8 adds support for top-level await by Gaelan on Tuesday September 24, 2019 @07:34PM (#21065873)
> two sequential lines of code are no longer two truly sequential instructions

How is this different from normal multithreading?

Re:V8 adds support for top-level await by eropple on Tuesday September 24, 2019 @07:41PM (#21065936)
Normal, which is to say preemptive, threading can pause your execution arbitrarily.

You can wedge a NodeJS interpreter by not yielding, but you also have control over when that yielding happens.

Re:V8 adds support for top-level await by _bxg1 on Tuesday September 24, 2019 @07:43PM (#21065953)
I suppose it's not, but it is different from normal JavaScript. I guess in multithreaded languages it's not such a big leap.
Re:V8 adds support for top-level await by desc on Tuesday September 24, 2019 @08:08PM (#21066149)
> But the general benefit you get from JS being single-threaded is the fact that any given callback is transactional. No other code will ever come in and mutate state between two regular lines of JavaScript code. Achieving this is pretty much the whole point of locking mechanisms, so under normal circumstances they aren't necessary for JS.

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.

Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @09:43PM (#21066741)
Javascript devs, for all their flaws, understand async programming far, far better than the average C programmer.

Indeed, your assertion that we need "locking primitives" to counteract "race conditions" is evidence of that. Yes, JavaScript can have race conditions, but not multi-threaded race conditions that cause resource contention [0].

So what good would locking primitives be?

And as a solution to single-threaded race conditions, it's becoming more and more common to use pure functions and immutable data structures in Javascript. In the ReactJS world, it's practically standard to use immutable data structures.

Furthermore, JS devs know how to structure their code, either using nested callbacks or promises, or now, async/await, to avoid async data races. Anyone who programs primarily asynchronously understands these things.

So in summary, it's pretty arrogant to assume that JS devs are going to have to "re-learn" the same things that C programmers learned in the 1990s (also ignorant, because 1990s C was decidedly synchronous). The only devs I know that struggle with race conditions in Javascript are those who are coming from another language or paradigm and who fundamentally fail to understand asynchronous programming.

0. The only exception to this would be those NodeJS devs who use multiple processes or browser devs using web workers along with the ultra-new and not very well-supported shared (memory) resources, both of which are rare in the JS world because there's not much need. You can achieve adequate performance off of one thread for practically any IO-bound application unless you're operating at Facebook scale. And in those case when people are using multiple processes for CPU bound algorithms, they're almost always using them with async message queues anyway, which obviates the need for any locking primitives.

Edit: https://news.ycombinator.com/item?id=21065831 in this case, async/await can introduce a problem. But using an immutable data structure reference as would almost always eliminate this issue.

Also, in practice you're probably using a database whose library has transactions, so you'd "lock" the transaction in this way.

But OK, if you use async/await or generators along with mutability, then a locking primitive could be useful, I'll concede. Although in a single-threaded program, a boolean is just as good.

Re:V8 adds support for top-level await by millstone on Tuesday September 24, 2019 @10:00PM (#21066834)
Here's an example of how you might get a race condition in JS:

    async function deduct(amt) {
        var balance = await getBalance();
        if (balance >= amt)
            return await setBalance(balance - amt);
    }
One way to resolve this would be with a mutex to protect the balance during the critical section (which is async). What would you suggest instead?
Re:V8 adds support for top-level await by Arelius on Tuesday September 24, 2019 @10:12PM (#21066898)
I mean the answer here is an async getBalance and setBalance is the incorrect way to do this, and a mutex won't solve that.

if balance is a javascript variable, access shouldn't be provided by async functions. In the case that mutex is a remote resource of some sort (file or network resource) a process local lock won't solve this for you either.

It seems to me that the lack of locks make forces software engineers to consider the nature of their data instead of just grabbing for the inappropriate os lock.

Re:V8 adds support for top-level await by ghusbands on Tuesday September 24, 2019 @10:29PM (#21067003)
More subtly, here's an example of a hidden race condition in JS:

  async function totalSize(fol) {
    const files = await fol.getFiles();
    let totalSize = 0;
    await Promise.all(files.map(async file => {
      totalSize += await file.getSize();
    }));
    // totalSize is now way too small
    return totalSize;
  }
Re:V8 adds support for top-level await by tlrobinson on Wednesday September 25, 2019 @12:51AM (#21067705)
I would have written it in a more functional way:

  async function totalSize(fol) {
    const files = await fol.getFiles();
    const sizes = await Promise.all(files.map(file => file.getSize()));
    return sizes.reduce((acc, size) => acc + size);
  }
Re:V8 adds support for top-level await by brlewis on Thursday September 26, 2019 @01:23PM (#21083163)
This is an interesting example because it demonstrates a way to confuse programmers that wasn't previously possible. Thanks for sharing it; it was a gem on an article otherwise full of confused comments.

After sharing it at work, someone pointed out that it isn't technically a race condition. The problem isn't caused by operations happening in an unpredictable order. It's unlikely that any of the promises are already resolved, so the lhs is always evaluated first. It's just that the programmer is surprised by the order of operations.

The takeaway is unsurprising: that `await` should be a signal to make one think carefully about state change; having `await` right after `+=` should be a big signal.

But the fact that an unwary programmer can actually be tripped up is interesting, despite some other comments on this article claiming such trip-ups are inevitable.

Re:V8 adds support for top-level await by fourthark on Tuesday September 24, 2019 @11:26PM (#21067318)
Because all the promises are waiting on `fileSize`, right?

But do you mean that JS 1. will read `totalSize` then 2. do the asynchronous call, then 3. add and set? Seems like it's ambiguous and JS could just as easily read `totalSize` after the call, and all would be OK.

Or is the ordering specified?

Thanks for this clever example!

Re:V8 adds support for top-level await by esprehn on Wednesday September 25, 2019 @12:25AM (#21067592)
The ordering is specified left to right.

  totalSize += await getSize()
becomes

  totalSize = totalSize + await getSize()
So all the map callbacks run one by one, read totalSize as 0, and then suspend waiting for getSize(). Each one then resolves and assigns totalSize to be 0 + the size.

The race is what order the getSize() calls return in since only the last one will control the return value. Otherwise the issue isn't a race but just a logical ordering bug.

(This isn't super different than doing array[two()] = one() since two will actually run first, so ex. array[i += 1] = i will modify i before assigning the value.)

Correct would be to change the body of the map to:

  const fileSize = await file.getSize();
  totalSize += fileSize;
or the whole function to:

  async function totalSize(fol) {
    const files = await fol.getFiles();
    const sizes = await Promise.all(files.map(file => file.getSize()));
    return sizes.reduce((totalSize, size) => totalSize + size, 0);
  }
Re:V8 adds support for top-level await by evmar on Wednesday September 25, 2019 @01:02AM (#21067758)
Wow, why would you define it this way. I had to check the spec because I didn't believe you. They got #2/#3 backwards.

https://es5.github.io/#x11.13.2

Re:V8 adds support for top-level await by XCSme on Wednesday September 25, 2019 @04:54AM (#21068966)
What if you change it to

  totalSize = await file.getSize() + totalSize; 
Would it be fine then?
Re:V8 adds support for top-level await by DrJokepu on Tuesday September 24, 2019 @10:10PM (#21066884)
I mean, if something like this is behind a promise or an async call, that probably means that there's I/O involved (no point using async to access local in-memory data), which means that the local locking primitive won't be incredibly useful, you're going to have to lock it via whatever mechanism the I/O channel (or some API using the I/O channel) provides, such as file locking or some API call or something.
Re:V8 adds support for top-level await by ithkuil on Wednesday September 25, 2019 @12:53AM (#21067711)
Yep, that's the benefit of having to explicitly mark each function explicitly as async. I've heard a lot of people complaining that it's painful, especially during refactorings since it bubbles up, but that's precisely the point: it allows you to know (and choose) whether a function can or cannot be executed interleaved with something else.

That said, if new developers just blindly use async/await without understanding what's going on ... well that's another problem.

The field is filled with foot-guns, for some definition of guns (and for some definition of foot).

The problem with C, or better the problem with pre-emptive multitasking in general, is that even relatively knowledgeable developers were constantly hitting subtle issues with the memory model. Consider the good old trap of efficiently lazy-initializing a Singleton in Java: https://en.m.wikipedia.org/wiki/Double-checked_locking#Usage...

Re:V8 adds support for top-level await by kaveh_h on Tuesday September 24, 2019 @11:42PM (#21067397)
If the balance is handled in a remote server or location then you should have a remote deduct method which handles the operation in an atomic idempotent manner.

If you are handling data locally e.g in a file then you would ideally rely on OS write lock and only store the deltas and never change state. You could then calculate the balance from the log of deductions and insertions.

FYI this problem has not anything to do with async or JS.

Re:V8 adds support for top-level await by candu on Wednesday September 25, 2019 @08:58AM (#21070288)
Don't have separate get / set functions?

    async function changeBalance(account, amt) {
        /* 
         * BEGIN;
         *
         * UPDATE accounts
         * SET balance = balance - amt
         * WHERE id = ${id};
         *
         * COMMIT;
         */
    }
Of course, this is just a mutex in database transaction clothing :)
Re:V8 adds support for top-level await by grumpy8 on Tuesday September 24, 2019 @10:14PM (#21066907)
>> What would you suggest instead?

On the front-end, I'd do a call to the server. On the server, I'd use a database transaction for that.

But I get your point. The way I deal with race condition in redux is to have a mutex. I.e. While something is being fetched, any call to this command is either ignored or queued up.

I use redux-saga for the "race-condition" and general flow, and call the various async functions from within sagas.

Re:V8 adds support for top-level await by abalone on Tuesday September 24, 2019 @10:49PM (#21067115)
How about an optimistic lock?

    async function deduct(amt) {
        var balance = await getBalance();
        if (balance >= amt)
           await setBalance(balance - amt);

        var newbalance = await getBalance();
        if (newbalance != (balance - amt)) {
            await setBalance(balance + amt);
            // tell the user the transaction failed...
        }
    }
Re:V8 adds support for top-level await by jtsiskin on Tuesday September 24, 2019 @11:43PM (#21067400)
This is getting off topic, but what would probably be best here is a test and set operation: setBalance(expectedBalance, newBalance)
Re:V8 adds support for top-level await by wruza on Wednesday September 25, 2019 @12:51AM (#21067704)
And they call average C developer less experienced in async. The only correct ‘update balance’ operation is:

  INSERT INTO CashFlow
    (date, income, expense)
  VALUES
    (:date, :income, 0)
Get balance:

  SELECT sum(income)-sum(expense) AS balance
  FROM CashFlow
What modern js still has to reinvent is in-client synchronizing storage which naturally resolves who waits on what (if at all). This is partially simulated by reactjs now by trading in a developer’s comfort at near zero price.

All this “await/promise has clear semantics and we think async” is pointless hope because code should never race with itself. Data should, and the storage must be there to kill associated problems once and forever.

Re:V8 adds support for top-level await by kristiandupont on Wednesday September 25, 2019 @06:27AM (#21069367)
Operational transformations are not the "only correct" way to update an integer and it comes with a number of performance and memory/storage consumption implications.

>What modern js still has to reinvent is in-client synchronizing storage which naturally resolves who waits on what (if at all)

I don't understand what this means but it makes me curious. What would be an example of this in one of the languages that have already "reinvented" it?

Re:V8 adds support for top-level await by wruza on Wednesday September 25, 2019 @09:32AM (#21070584)
SQL.

>it comes with a number of performance and memory/storage consumption implications.

Instead of selecting sum(), CREATE TRIGGER then and update a singleton on insert. You can even create a view with N recent transactions and catch inserts into it to maintain that N, if you don’t want a full history.

Re:V8 adds support for top-level await by tlrobinson on Wednesday September 25, 2019 @12:31AM (#21067616)
How about an `updateBalance(updateFn)` that is protected by a mutex:

    async function deduct(amt) {
      await updateBalance(balance => {
        if (balance >= amt) {
          return balance - amt;
        } else {
          throw new Error("not enough balance");
      })
    }
Re:V8 adds support for top-level await by random314 on Wednesday September 25, 2019 @04:36PM (#21075099)
Mutex specifically refers to OS/processor level constructs that protect critical sections using algorithms such as the bakery algorithm or special CPU instructions. None of these are required to protect the critical section in the example above.

You can just use a busy flag. I am not familiar with JS, so this is approximate syntax.

while (busy) { await busyIsFalse } busy = true

Critical Section

busy = false

notify busyIsFalse

Simple boolean flags will work

Re:V8 adds support for top-level await by winrid on Wednesday September 25, 2019 @12:42PM (#21072474)
Use the datastore... use atomic updates and transactions update balance, where balance >= amount, set balance = x
Re:V8 adds support for top-level await by spraak on Wednesday September 25, 2019 @01:01AM (#21067752)
Can anyone explain this to me, how is it a race condition?
Re:V8 adds support for top-level await by pmontra on Wednesday September 25, 2019 @01:56AM (#21068059)
Two concurrent calls to that code. Both of them get the same balance (100), pass the test and deduct the amount (75), getting to a -50 balance.
Re:V8 adds support for top-level await by pmontra on Wednesday September 25, 2019 @07:02AM (#21069515)
I correct myself. It leads to a 25 balance but possibly a double spend of those 50 dollars in two transactions.

The standard pattern is to use database row locking or calling a stored procedure that performs locking inside. Backend developers typically don't like the second solution but it provides a kind of API. Not to be overlooked if there are multiple services accessing it, especially in a polyglot environment.

Re:V8 adds support for top-level await by vore on Tuesday September 24, 2019 @10:03PM (#21066846)
Just because code is written in an asynchronous style doesn't prevent correctness errors that would not exist with locking. For instance, using everyone's favorite example, a bank:

In no particular order:

    1. Pizza company debits by balance by $5
    2. I withdraw $5
Assuming both those operations can yield due to e.g. async requests and that they can be resumed at any point, I don't know which order they will yield or resume in.

Consider this interleaving:

    1.1. Get balance, I have $5
    2.1. Get balance, I have $5
    1.2. Set balance to $0
    2.2. Set balance to $0 (but there are total debits of $10!)
With locking:

    1.0. Acquire account lock
    1.1. Get balance, I have $5
    2.1. Get balance: wait on lock
    1.2. Set balance to $0
    1.3. Release account lock
    2.1. Get balance, I have $0
    2.2. Can't set balance, will overdraw!
With regards to resource contention, anything that causes a waiter graph cycle can cause deadlocking, regardless of single- or multi-threading. I can't think of a compelling example here, but nobody expects to have deadlocks yet they still happen :)
Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @11:40PM (#21067387)
> With regards to resource contention, anything that causes a waiter graph cycle can cause deadlocking

Maybe I'll learn something here, but can you explain how an async runtime with no locking primitives like JS could cause a waiter graph cycle?

Re:V8 adds support for top-level await by random314 on Wednesday September 25, 2019 @04:49PM (#21075277)
You don't need locking primitives for a deadlock. Await is sufficient.
Re:V8 adds support for top-level await by DecoPerson on Tuesday September 24, 2019 @10:32PM (#21067020)
An example of a deadlock that continues the bank balance analogy:

A transfer funds method that does: 1. Lock account A 2. Get balance and check 3. Lock account B 4. Debit account A 5. Credit account B

If the two accounts transfer to each other at the same time, you can get: 1. A->B: Lock account A 2. A->B: Get balance and check 3. B->A: Lock account B 4. B->A: Get balance and check 5. A->B: Lock account B -- deadlock

One solution is to always acquire locks in the same order and release all of them when you fail to obtain one. You can do this in the transfer example by sorting by account ID (so always lock A before B).

Re:V8 adds support for top-level await by Arelius on Tuesday September 24, 2019 @10:15PM (#21066913)
Sure enough, but in javascript if your balance operations can yield due to async requests than you already have a bigger problem than a shared in-memory variable. Which is really the only case a standard os-lock can solve.

Let's say that balance is in a database or behind a REST api. An os-lock won't solve the problems of other processes trying to update that same resource.

Re:V8 adds support for top-level await by vore on Tuesday September 24, 2019 @10:22PM (#21066960)
You can imagine a function being unintentionally promoted into async-world for some kind of cross-cutting concern even if the data is stored in memory, e.g. if logging requires an async call, or if reporting metrics requires an async call.

While I agree that in the general practice in JavaScript programming stops you from this kind of footgun, you can still be caught off-guard if you end up thinking that this programming style is immune to this kind of problem: locking is a big hammer with its own problems, but it will never cause data incorrectness.

Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @10:23PM (#21066967)
> Just because code is written in an asynchronous style doesn't prevent correctness errors that would not exist with locking.

Yes, that's why I referenced passing around a reference to an immutable data structure, which is fairly common in JS.

Re:V8 adds support for top-level await by vore on Tuesday September 24, 2019 @10:24PM (#21066972)
How will you update the bank account with an immutable data structure? You need to mutate the balance somewhere.
Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @10:31PM (#21067014)
Well, in practice you're presumably using a database, which has transactions in the API you're using.

But to answer your question, in functional programming (also frequently ReactJS, if you use libraries like Redux), you don't mutate the data structure. You create a new data structure based off the old one.

Am I misunderstanding your question? Just because you use an immutable data structure doesn't mean it can't be updated.

Re:V8 adds support for top-level await by dboreham on Tuesday September 24, 2019 @11:09PM (#21067231)
Database written in not(Javascript) of course, since it has to work and handle concurrency properly.
Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @11:25PM (#21067317)
Yes, of course. Why would you build a database in JS?

And does gmail not work well for you?

Most of us are only using JavaScript because that's the only way to build browser applications (or compile-to-JS languages, which still require an understanding of JS or you'll run into problems).

In any case, I don't particularly like JS, so your dig fell short. But JS is necessary for many of us.

Re:V8 adds support for top-level await by zo1 on Wednesday September 25, 2019 @07:27AM (#21069649)
> "Most of us are only using JavaScript because that's the only way to build browser applications (or compile-to-JS languages, which still require an understanding of JS or you'll run into problems)."

The javascript apocalypse is inevitable, we're just yet to reach the tipping point where this hard-requirement to use JS for web/browser applications is no longer there.

Re:V8 adds support for top-level await by kbenson on Tuesday September 24, 2019 @10:12PM (#21066902)
> also ignorant, because 1990s C was decidedly synchronous

There was definitely threading in C in the 1990's, and threads are asynchronous by nature. There's a whole class of synchronization bugs that C and C++ developers had to learn to deal with, and while JavaScript developers get to avoid some by the nature of there being a single thread, that doesn't necessarily exempt them from all of them.

Re:V8 adds support for top-level await by jnbiche on Tuesday September 24, 2019 @11:07PM (#21067220)
Threading implementation was async, not the programming model. Async programming style/model(as far as I'm aware) refers to the use of callbacks or coroutines, neither of which was common at all in C in the 1990s.

Indeed, nginx caused big waves due to its superior IO performance as the first popular async http server released in 2004.

But correct me if you have a different understanding of the term.

Re:V8 adds support for top-level await by tanr54ok on Wednesday September 25, 2019 @06:02AM (#21069272)
Your post manages to say nothing correct. Javascript did not invent callbacks, asynchronous programming, or event driven programming. Nodejs popularized it in the 2010s but you are claiming credit for something that has been in common use since the 70s. I have no clue how anyone can speak with such authority while clearly having not done a cursory glance at programming APIs available in the 80s and 90s.

Lighttpd was released before nginx and was wildly popular for a good while. Not to mention other servers like AOLserver in the 90s.

The use of non blocking sockets was nothing new, used heavily in C code throughout the 90s and is the basis of asynchronous processing (refer to Stevens Unix network programming). You should also read documents by John Ousterhout written in the 90s about this topic.

Now just about every major GUI library was targeted in C or Pascal and used an event driven callback model. You can refer to the Windows API, the modern Mac carbon API which was developed in the 80s at Next and the older Mac SDK. The Windows SDK allows event driven programming for UI and IO. tcl/tk. Just about anything on top of X (ie Motif, GTK). The list will go on and on.

Do some research before making bold claims about C programmers not understanding asynchronous programming and callbacks.

Also I think the other reply is correct in asserting that your definition of async is making distinctions without differences.

Eg from 1995: https://web.stanford.edu/~ouster/cgi-bin/papers/threads.pdf Note that event driven programming was nothing new in 1995.. it was the basis for the Windows api designed years prior after all, but this was around the time that multithreading was pushed for everything.

Heck fibers: https://docs.microsoft.com/en-us/windows/win32/procthread/fi... have existed in the Windows api since at least 95.

Re:V8 adds support for top-level await by kbenson on Wednesday September 25, 2019 @12:47AM (#21067688)
I'm referring to Os level threads, but functionally it's no different if we talk about forking with shared memory. Given that the vast majority of computing 15 years ago was single processor and multi-process or multi-thread for ansynchronous behavior (or use of select), it's all functionally equivalent, and many of the same problems discovered many decades ago for multi-process programs apply.

If I have a main program, and I fork a child to handle a task, and use shared memory to communicate, how is that any different than Javascript executing and and async call that sets or returns a value? The Javascript runtime has the same behavior as the OS scheduler in this, and if there's a single processor, it necessarily will only execute one instruction at a time. There's still plenty of pitfalls to worry about and that's why locks were (and are) useful, and why they are included with OS thread implementations (which are really just a nice API on forks and shared memory often dealt with by the OS for additional benefit).

Re:V8 adds support for top-level await by matharmin on Wednesday September 25, 2019 @03:05AM (#21068438)
The most common need I've found for some kind of locking mechanism in JavaScript is the very basic example of asynchronous functions triggered by UI. For example, button -> http call -> navigate. If the user presses the button twice, the call is made twice before navigating, which has unexpected results in some cases.

The lock in this case could be as simple as disabling the button as soon as the user clicks on it, but I've also find Promise-based locking functions to be very useful in solving these kind of problems.

I wouldn't call these "primitives" though - you don't need a language construct for it. It's easy enough to build the locks as functions working with Promises.

Re:V8 adds support for top-level await by jcelerier on Wednesday September 25, 2019 @02:49AM (#21068357)
> (also ignorant, because 1990s C was decidedly synchronous)

unless you were programming in... any GUI toolkit ever except the most toy ones ? even win 3.1 GUI primitives were async

Re:V8 adds support for top-level await by jnbiche on Wednesday September 25, 2019 @04:14PM (#21074825)
Yeah, fair point. Technically, windows was C++ but close enough, plus as you say other Gui toolkits also used async. And I should have remembered that since Gui libraries used async for the same reason as browser JS. It's not good to block the UI thread.

I was thinking about C network programming, which despite what people are saying on here, was not event driven in the 1990s (I was there).

Re:V8 adds support for top-level await by paulsutter on Wednesday September 25, 2019 @08:42AM (#21070177)
> 1990s C was decidedly synchronous

You know that Javascript is a C(++) program? That when you use TCP, the protocol is in C? The ethernet driver is written in C? The OS scheduler is written in C? That you're programming in a little sandbox, and all the concurrency around you in managed in C?

There has never been anything synchronous about C.

Re:V8 adds support for top-level await by jnbiche on Wednesday September 25, 2019 @02:29PM (#21073679)
> You know that Javascript is a C(++) program?

JavaScript is not implementation-defined. There's a spec, and there are interpreters in a number of different languages. Sure, most common JS interpreters/JIT and otherwise, are in C++, but that says nothing about whether or not they use an event-driven style underneath to program the interpreter.

And regarding C being synchronous, I'm talking about programming models, not underlying architecture of the computer or OS. If everything is programmed in async as you seem to suggest here, then we do we bother distinguishing the two? Why do most books on networking programming have a separate chapter devoted to async or event-driven programming? Why does the Unix socket API have `socket.setblocking(0)`?

But I suspect you know damn well what I'm talking about.

I've got to get off of social media.

Re:V8 adds support for top-level await by paulsutter on Wednesday September 25, 2019 @05:37PM (#21075800)
No matter what language you are using, concurrency happens in instructions, interrupts, cores, caches, devices, virtual memory mechanisms, etc, not even getting into GPU architecture. In C you have direct control over these things, you can make the system as concurrent as you want.

In Javascript you have a little window into this through whatever the layer below provided you. So Javascript by definition has a (small) subset of the concurrency you can get in a systems language.

And no I'm not entirely sure what you're talking about. It sounds like you learned concurrency in Javascript, and define concurrency in terms of Javascript primitives. But that's merely a guess on my part.

Re:V8 adds support for top-level await by zo1 on Wednesday September 25, 2019 @07:24AM (#21069638)
> "Javascript devs, for all their flaws, understand async programming far, far better than the average C programmer."

Not the junior ones, they don't. I understand the need to want to talk about this advanced topic with only perfect and good developers, but this is almost never the case in concrete scenarios. Developers forget, developers share code with other developers and the consequent spaghetti mess is hard to reason about 100%, developers make mistakes, developers are sometimes yet to learn something, developers miss small bugs, new code deals with library code that might have an async bug, etc.

Sequential flow is much easier to reason about and control for, and if you ask for my opinion, we should only use async features if the benefits of their usage far outweigh the potential complexity and headache that they introduce if you don't "use them the right way".

Re:V8 adds support for top-level await by random314 on Wednesday September 25, 2019 @12:12PM (#21072120)
I am not sure how the parent comment has received so many upvotes. OP has some fundamental misunderstanding of mutexes and the purpose of async io.

Locking primitives are completely unnecessary in any single threaded program. Also mutexes in C or otherwise are way older than 1990. They go back to the 1950s.

When your program is single threaded, a simple boolean flag variable can act as a mutex, you don't need a mutex primitive for this. 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.

Infact, the entire purpose of async programming is to stop the use of mutexes and multiple threads to perform concurrent io, which is something that can be performed by a single thread. This is the foundational premise of nodejs.

Re:V8 adds support for top-level await by drevil-v2 on Tuesday September 24, 2019 @06:31PM (#21065340)
I think JS devs usually use async/await for network calls and not really for computation or file access. Those type of applications are better served by other languages.

A deadlock in the problem space that JavaScript operates in would be a rarity I feel.

Re:V8 adds support for top-level await by ChristianBundy on Tuesday September 24, 2019 @07:53PM (#21066045)
I've been using async/await especially for file access.

    const fs = require('fs').promises
    const path = require('path')
    
    const filePath = path.join(__dirname, 'package.json')
    const fileContents = await fs.readFile(filePath, 'utf8')
    
    console.log(fileContents)
Re:V8 adds support for top-level await by drevil-v2 on Tuesday September 24, 2019 @08:32PM (#21066327)
Well I personally would use a synchronous method rather than async for something like that where the next code path depends on the return data; if you can't get the file contents then you want to throw an error straight away.
Re:V8 adds support for top-level await by galaxyLogic on Tuesday September 24, 2019 @09:57PM (#21066817)
That would seem to make sense, using readFileSync() instead.

I assume it would really do much the same thing as the await example. Is there any difference?

If they do the same thing, then what's the benefit of async/await? I guess it's that you can now write your own "fs.readFileSync()" or something like that in JavaScript when needed.

Re:V8 adds support for top-level await by jeswin on Tuesday September 24, 2019 @10:56PM (#21067153)
fs.readFileSync() blocks the event loop preventing any other code from being run or other events/requests from being processed. Await yields control back so that other requests can be processed while the file is being read.
Re:V8 adds support for top-level await by galaxyLogic on Wednesday September 25, 2019 @09:54AM (#21070773)
Ah I see. Genius. So is there ever a use-case for NOT using the Async/Await -version of readFile()? Is fsReadSync() now just legacy which we don't need any more?
Re:V8 adds support for top-level await by ihuman on Tuesday September 24, 2019 @08:35PM (#21066352)
Is there an advantage to using "await fs.readFile" over "fs.readFileSync"?
Re:V8 adds support for top-level await by mayank on Tuesday September 24, 2019 @08:42PM (#21066390)
You haven't blocked the entire event loop (i.e., other async functions that may be waiting on I/O) while the file is being read.
Re:V8 adds support for top-level await by hombre_fatal on Tuesday September 24, 2019 @11:09PM (#21067225)
Synchronous code blocks the event loop, so in real world code you'd only use the sync-version in some special cases, like when you're reading config files before you "server.boot()" and other scenarios where it's just a one-time cost.

Though the few sync functions in the stdlib become less and less convenient, first when promises came out, then with async/await, and now with top-level await.

Re:V8 adds support for top-level await by jdm2212 on Tuesday September 24, 2019 @06:44PM (#21065438)
JS is single-threaded right so (...I think...) deadlocks are actually impossible. Although I guess you can still get stuck if two pieces of code are waiting on each other to satisfy some condition (and trading control of the sole thread) without using explicit locking.
Re:V8 adds support for top-level await by matharmin on Wednesday September 25, 2019 @03:08AM (#21068455)
Deadlocks can happen in any kind of concurrent system with locks. As soon as you start building & using locks with Promises, this is quite easy to happen.

That said, the need for locks in JavaScript is much less than in a typical multi-threaded language, so it's a lot easier to keep track of and avoid deadlocks, if you're even using locks at all.

Re:V8 adds support for top-level await by foota on Tuesday September 24, 2019 @07:08PM (#21065641)
Yeah, nothing about JavaScript being single threaded implies any resistance to deadlock.
Re:V8 adds support for top-level await by jdm2212 on Tuesday September 24, 2019 @07:34PM (#21065878)
It's still much harder to deadlock single-threaded code than multi-threaded code because single-threading eliminates the need for most locking, which in turn eliminates most opportunities to cause deadlocks.
Re:V8 adds support for top-level await by taf2 on Tuesday September 24, 2019 @08:26PM (#21066273)
I love setTimeout
Re:V8 adds support for top-level await by ChrisSD on Tuesday September 24, 2019 @09:24PM (#21066641)
What does setTimeout have to do with deadlocks?
Re:V8 adds support for top-level await by james-mcelwain on Tuesday September 24, 2019 @06:51PM (#21065499)
Despite other deficiencies, JavaScript devs are generally pretty good at writing async code, for the simple reason that you get burned suuuper quickly if you try to mutate state outside of an async context when doing things like adding click handlers. This is something most front-end devs learn pretty quickly since it's such a fundamental part of writing JavaScript.
Re:V8 adds support for top-level await by rictic on Tuesday September 24, 2019 @07:01PM (#21065587)
I don't know if you intended to, but it comes across as arrogant and dismissive.

Dealing with asynchrony has been the an issue in JavaScript from the beginning, as the fundamental model is event driven.

There are a number of mechanisms for coordinating work, including mutexes (see Atomics.wait()), but for >99% of code, Promises are a better option.

Re:V8 adds support for top-level await by anderskaseorg on Tuesday September 24, 2019 @07:00PM (#21065580)
Since it’s all single-threaded, you can write your own locking primitives and they will work correctly.
Re:V8 adds support for top-level await by pseudoramble on Tuesday September 24, 2019 @07:21PM (#21065759)
> It doesn't matter that it's all single threaded if all your function calls may or may not block and run a bunch of other code in the meantime, mutating all kinds of state.

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...

Re:V8 adds support for top-level await by shanemhansen on Tuesday September 24, 2019 @07:28PM (#21065831)
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()
    }
Re:V8 adds support for top-level await by hombre_fatal on Tuesday September 24, 2019 @08:32PM (#21066315)
I 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.
Re:V8 adds support for top-level await by vidarh on Tuesday September 24, 2019 @10:17PM (#21066927)
You'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.
Re:V8 adds support for top-level await by jeswin on Tuesday September 24, 2019 @11:02PM (#21067191)
Yeah 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.
Re:V8 adds support for top-level await by vidarh on Wednesday September 25, 2019 @03:43AM (#21068660)
It 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.

Re:V8 adds support for top-level await by olalonde on Wednesday September 25, 2019 @02:26AM (#21068228)
In that case, the code would be sync and there wouldn't be a "race condition".
Re:V8 adds support for top-level await by vidarh on Wednesday September 25, 2019 @03:45AM (#21068670)
I 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.

Re:V8 adds support for top-level await by olalonde on Wednesday September 25, 2019 @06:57AM (#21069490)
While 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

[1] https://github.com/sindresorhus/p-queue

Re:V8 adds support for top-level await by pseudoramble on Tuesday September 24, 2019 @07:54PM (#21066047)
Good 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.

Re:V8 adds support for top-level await by blacksoil on Tuesday September 24, 2019 @09:39PM (#21066718)
Good example. I think this should be handled by whatever data store is being used, though. In case of SQL, transaction.
Re:V8 adds support for top-level await by asoo on Wednesday September 25, 2019 @03:02AM (#21068422)
You'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.
Re:V8 adds support for top-level await by trust07007707 on Tuesday September 24, 2019 @07:22PM (#21065773)
There is an Atomics API for that...
Re:V8 adds support for top-level await by Izkata on Tuesday September 24, 2019 @07:08PM (#21065639)
Oh, you're going to love (hate) what I suspect will happen then:

> race conditions

I've gotten into multiple arguments with first-language-javascript devs who think race conditions can't happen in javascript "because it's not multithreaded". I'm thinking before they can accept this type of bug, it's going to get a new trendy name first, then all the old knowledge of "race conditions" continue to be ignored.

Re:V8 adds support for top-level await by minitech on Tuesday September 24, 2019 @06:45PM (#21065444)
> All this async code without decent locking primitives is leading to a rabbit hole of race conditions...

I haven’t seen this happening. Maybe it’s because I tend to use Node for web apps too much, which don’t have much shared state within Node to begin with? That it’s single-threaded does matter immensely, though, because if you have shared state that can be updated synchronously, you just write the code and know that it runs as a unit.

Re:V8 adds support for top-level await by wayneftw on Tuesday September 24, 2019 @06:59PM (#21065567)
In a single-threaded universe, what more do you need to lock the world than a boolean variable?
Re:V8 adds support for top-level await by nitely on Tuesday September 24, 2019 @09:14PM (#21066591)
Since booleans can't be awaited until their state is negated/toggled, I'd say a lock that works like a lock is needed.
Re:V8 adds support for top-level await by wayneftw on Tuesday September 24, 2019 @10:05PM (#21066858)
So use a Promise like a lock then. If it exists, wait on it. If not create one…

Of course you don’t need to use the existence of a Promise as your Boolean in this case. You can simply use the Boolean state in addition to the Promise because your code is not going to yield while atomically setting one Boolean variable.

Re:V8 adds support for top-level await by nitely on Wednesday September 25, 2019 @12:32AM (#21067623)
That would unblock all coroutines waiting for the promise, instead of just the first one. It's not that trivial, that's the point.
Re:V8 adds support for top-level await by esprehn on Wednesday September 25, 2019 @12:59AM (#21067740)
For queued ordering instead of unblocking everyone at once you can reassign the Promise.

  let lock = Promise.resolve()
  const wait = (callback) => lock = lock.then(() => callback());
Until the callback resolves the lock is held, so you can do:

  wait(async () => {
    await step1();
    await step2();
    // release the lock.
  });
Re:V8 adds support for top-level await by nitely on Wednesday September 25, 2019 @02:45AM (#21068336)
If the callback throws an error, none of the awaiting coroutines will run, and adding a catch would break the callback error propagation.
Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @09:17AM (#21070449)
Yes, but he was giving the simplest implementation for the sake of brevity.

Also, nobody said that we need callback error propagation in a priority queue to act the same exact way that callback error propagation works in a straight promise chain... but if you just want to reject all waiters in case of an error, you can do that.

Take a look at sindresorhus' p-queue project - https://github.com/sindresorhus/p-queue - which is about 40 lines of code that take care of queueing exactly the way you want. And it's easy to modify if you want to reject the whole queue in case of an error, which is something that I've done myself in the past. You can see them discussing the possibility of that feature right here - https://github.com/sindresorhus/p-queue/issues/29

Long story short: Your problem is with ordering, not locking.

Re:V8 adds support for top-level await by nitely on Wednesday September 25, 2019 @01:45PM (#21073172)
Even the original solution is a lot more complex than just checking some primitive value. A lock is still needed when doing concurrency on a single thread, which is the point I'm making anyway.

> Long story short: Your problem is with ordering, not locking.

Look up the definition of a concurrency lock.

Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @01:31AM (#21067907)
You moved the goal posts on me. But the solution for your next scenario is actually pretty trivial though isn’t it?

You don’t really need what you call locking in a single-threaded world. You just need a state machine.

Re:V8 adds support for top-level await by nitely on Wednesday September 25, 2019 @02:47AM (#21068350)
No, I didn't. That's how a lock works. Only one coroutine (or thread) is allowed to hold it at a time while the others must await their turn. A state machine is a lot more than a boolean variable.
Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @09:02AM (#21070325)
Both of your statements are incorrect. Locks are an abstract concept that have many different behaviors. In a multi-threaded world (not JS) a certain type of synchronization lock, such as a single-writer/multiple-reader locking mechanism would specifically allow you to unblock all waiting coroutines. Another type of lock might only allow the first waiter to unblock.

There simply is no concurrency in JS though, so there is no need for locking. There is no way that you would ever "unblock all coroutines waiting for the promise" because you just can't run more than 1 coroutine at a time. So your problem is with ordering, not concurrency and locks are a solution for concurrency.

And the simplest state machine is a boolean variable.

Anyway, I guess if you still disagree then you can go and ask the people who build JS engines why they don't want to add locks. Maybe ask the v8 team - they add stuff that isn't in the spec all the time and they haven't seen a big need for this, so they must know the answer...

Re:V8 adds support for top-level await by nitely on Wednesday September 25, 2019 @01:56PM (#21073313)
First look up the definition of a lock and mutual exclusion. There are two types of basic locks: mutexes and semaphores. Both keep two threads or coroutines from executing a block of code/instructions at the same time, and both work in a similar way I already described.

> There simply is no concurrency in JS though,

There is no parallelism in JS (well there is with workers now), concurrency is not parallelism.

Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @05:07PM (#21075486)
Show me a piece of code where you think you need locks I guess.

I will concede that I got the two terms confused, mostly because I haven’t had to think about this kind of stuff in years because there’s literally nothing I’ve ever had to do with JavaScript that’s required me to think about it.

But I’d really love to see some code where you can’t handle concurrently waiting coroutine priorities in a single threaded world. Concurrency without parallelism is not really a problem the way I see it.

Re:V8 adds support for top-level await by eropple on Tuesday September 24, 2019 @07:43PM (#21065961)
There are more and more valuable locking mechanisms than a bool. Read-write locks, for example, are still sometimes (not often but I've done it on occasion) valuable in NodeJS.
Re:V8 adds support for top-level await by manigandham on Tuesday September 24, 2019 @09:39PM (#21066715)
Only the single JS thread is ever reading or writing a value so what is the lock for?
Re:V8 adds support for top-level await by eropple on Tuesday September 24, 2019 @10:58PM (#21067171)
You get that it's super common to await in the middle of something that could be considered a critical section, yeah? And that doing so will result in a different coroutine (for lack of a better term; that's what Promise-driven code effectively is) being executed until control returns to the awaited Promise within that critical section?
Re:V8 adds support for top-level await by manigandham on Tuesday September 24, 2019 @11:15PM (#21067260)
Yes but async/await is for concurrency, not parallelism. Only one or the other "coroutine" will run at any time so what is the lock protecting? There's no way for multiple branches of code to access the same variable at the same time in JS. What else would a reader/writer lock be used for?
Re:V8 adds support for top-level await by eropple on Wednesday September 25, 2019 @12:13AM (#21067532)
Locks are for concurrency. Parallelism is orthogonal.

When you are forced to yield inside of your critical section (a database call, a file write, whatever), as is common in NodeJS, you must acquire a lock that another coroutine can't run through.

Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @01:53AM (#21068037)
He got the terms wrong but he’s absolutely right.

> There's no way for multiple branches of code to access the same variable at the same time in JS.

This is 100% true and it’s the very reason why we do not need locks. There is no thing called a critical section in JavaScript because in JavaScript all of your code runs on a single thread and it’s all equal therefore none of it is more volatile than any other piece code. All that you need to manage asynchronous concurrency is good state management, not “locks”, because there is nothing to lock since there are not multiple threads asking any of your JS code for control of execution at the same exact time. It’s all scheduled for you onto your single thread.

With that, what you don’t get in JS of course, is shared-state parallelism.

Re:V8 adds support for top-level await by eropple on Wednesday September 25, 2019 @02:07AM (#21068120)
None of the concerns of concurrency go away because you don't have threads, they go away when you have no multitasking, and NodeJS is a cooperatively multitasking environment (modulo a few minor asterisks).

So of course there are still critical sections--a critical section is a set of operations that must have uninterrupted access to a resource in order to maintain a desired level of consistency. If you have to yield (that is, `await` on a Promise or wait for the invocation of a callback), which sometimes you do have to do (i.e., you have to do IO, you've got a crunchy computation running in C++ outside of the JS loop, whatever), then you need to prevent access to that resource from whatever is being picked up next by the JS runtime while the first user is waiting for that IO to finish or that computation to come back.

To that end, locks are that "good state management" to which you refer. Most of the time one can use a mutex--the `async-lock` library is common--but I've even encountered cases where resource management must occur in structured forms and it was necessary to implement a read-write lock, where multiple readers can operate in tandem but all readers must close out before a writer can take control.

The basics don't change when your multitasking is cooperative rather than preemptive--all the same stuff still applies. This is not a controversial set of assertions. I don't understand at all where you're coming from.

Re:V8 adds support for top-level await by manigandham on Wednesday September 25, 2019 @04:22PM (#21074915)
Your note about 'async-lock' NPM package clears up the confusion. That's used to serialize operations to external systems, with their readme showing an example with multiple Redis calls within await blocks setting the same key incorrectly.

This conversation was talking about access to variables and data within Javascript code itself. Any language is open to data races when dealing with external systems. I can see why having a `lock` construct can be helpful here, but it seems extraneous when the actual language doesn't support concurrent access and simple packages already exist. The 'async-lock' is just a wrapper around a few variables, and only works precisely because JS is already single-threaded and serialized.

Re:V8 adds support for top-level await by wayneftw on Wednesday September 25, 2019 @08:47AM (#21070219)
When you have to bring C++ into the conversation to make your point, then it sounds like it’s a problem for C++…

There simply is no concurrency in JavaScript, so lock primitives are not solving any problem that JavaScript has.

Can you give an example of some code where you think locks would help you?

Re:V8 adds support for top-level await by nickitolas on Wednesday September 25, 2019 @06:59AM (#21069499)
I don't think a critical section is necessarily related to parallelism, but concurrency (As shown in other examples in this thread). If you google for "critical section", the first link (Wikipedia) begins "In concurrent programming,".

Regardless, if you were to have need of a lock, which was the basis of this chain of comments unless I'm missremembering, a read-write lock could improve performance, regardless of things being singlel threaded, precisely because of things being async (If you have N async tasks all read-only locking an rwlock, they can go on to fire async requests, which will run concurrently, despite the execution being single threaded).

Re:V8 adds support for top-level await by sosodev on Tuesday September 24, 2019 @07:22PM (#21065778)
Nothing really, but you'll still want be sure the lock is released correctly.
Re:V8 adds support for top-level await by tlrobinson on Wednesday September 25, 2019 @12:20AM (#21067569)
JavaScript uses non-preemptive multitasking. It's trivial to write locking primitives. Indeed there are already many: https://www.npmjs.com/search?q=mutex

You could even have a method decorator (https://github.com/tc39/proposal-decorators) that emulates Java's "synchronized" keyword.

Re:V8 adds support for top-level await by duxup on Tuesday September 24, 2019 @06:23PM (#21065269)
Isn't every new language doomed to relearn everything in some way?
Re:V8 adds support for top-level await by verisimilitudes on Tuesday September 24, 2019 @07:26PM (#21065813)
Not everyone uses languages designed by people with no place designing a language and taken from there. Not every language is PHP, JavaScript, C++, et al.

Take a look at a Lisp, APL or a derivative, or Ada for examples of languages designed by people who knew what they were doing. Lisp grew in universities under the direction of hackers and there are several standard dialects now. APL was designed by a mathematician originally as a teaching aid. Ada was designed in a contest by the US DoD after spending a long while collecting requirements and it was then fully specified before any implementation work was done.

Re:V8 adds support for top-level await by munificent on Tuesday September 24, 2019 @08:18PM (#21066217)
> by people who knew what they were doing.

The Great Old Ones did not have access to any lost mystical knowledge. The Lisp inventors hadn't even sorted out variable scoping leading many Lisps to have dynamic scoping which is now widely agreed to be the wrong default. Also, the Lisp-1 versus Lisp-2 split. These were clearly people that were figuring it out as they went along, which they would be the first to admit.

> standard dialects

An oxymoron if there ever was one. :)

> APL was designed by a mathematician originally as a teaching aid.

...and it's virtually dead. There are many interesting ideas in there but it doesn't seem like Iverson's ideas about what is a desirable notation for thought resonated with any significant fraction of people. There were many years where APL was a standard part of a CS degree. With that much forced contact, you would expect it to have stuck around if it had so much going for it. (See: Pascal and BASIC, which punched above their weight in large part because of early introduction in schools.)

> Ada was designed in a contest by the US DoD after spending a long while collecting requirements and it was then fully specified before any implementation work was done.

Ada also has a lot of interesting ideas but... outside of the DoD, it's not exactly doing well either. It turns out that spending several years writing an enormous specification without regard to implementation leads to... unbelievably complex, expensive implementations that take years to reach the market. Who could have predicted that?

The economic cost of implementing the language is a salient part of its fitness. It doesn't matter how nice the language is if you can't actually use it because you don't have a working compiler.

C++ is only bad to the degree that you can completely wish away path dependence. (Hint: You can't.) There were many languages better than C++ designed at the time, but they didn't take off because they lacked the features we hate in C++ today. Those features existed as a path to lead the existing C programmers to C++. Meanwhile, other purer languages floated off in space, beautiful but unreachable by mortals.

JavaScript was just a sad rush job that we're all unfortunately stuck with. I agree with you on PHP.

Re:V8 adds support for top-level await by verisimilitudes on Tuesday September 24, 2019 @09:08PM (#21066563)
The Great Old Ones did not have access to any lost mystical knowledge.

They at least had the advantage of not being forced to design a language for a failed startup, to be easy to learn where that's defined as resembling C, and other asinine considerations people continue to take into account nowadays.

These were clearly people that were figuring it out as they went along, which they would be the firt to admit

I'm not claiming otherwise, but the birth of Lisp was a paper written by an AI researcher that had good thought poured into it, not something meant to look like C and be good enough for whatever its purpose was on a strict time schedule.

...and it's virtually dead.

Why would that matter? Why does every language need to appeal to many people? Why is that a desirable quality? There's still plenty of APL being written. I even have an article concerning this: http://verisimilitudes.net/2019-08-08

Who could have predicted that?

Ada was designed to be implemented efficiently and understood easily. It's harder to write a good C or C++ compiler than it would be to write a good Ada compiler; there's simply fewer people doing this and GNAT is popular. The only compiler I'm aware of for C that anyone actually uses, sans the behemoths of GCC and Clang/LLVM, is tcc, and almost no one uses that. There's more Common Lisp implementations that are actually used than there are for C and the only reason you'd see more C compilers for Ada is because it's a smaller language with a standard that lets the implementor get away with doing so little at the cost of every program written in the language and because the standard for a C compiler is so low that it's expected to have bugs.

The economic cost of implementing the language is a salient part of its fitness.

When you start judging something in computing by its fitness, it reminds me of a virus, and that reminds me of The UNIX-HATERS Handbook and its description of C and UNIX.

C++ is only bad to the degree that you can completely wish away path dependence.

I don't know what you mean by this. Common Lisp doesn't care how code is loaded and Ada has a nice with and use system.

I do know C++ is a gargantuan language where the very grammar is ambiguous and is parsed differently by different compilers and I'm also aware it has little in static analysis due to this. Debugging Common lisp is simple due to its interactivity and Ada was designed to be statically-analyzed.

Part of why I mentioned APL, Lisp, and Ada is because I use all three of these languages. I could care less what the masses use. Do racecar drivers care about the people driving vans? Vans are more popular than racecars. Why should computing be different?

Re:V8 adds support for top-level await by why_only_15 on Tuesday September 24, 2019 @09:50PM (#21066785)
There are at least three other C/C++ implementations - AMD and Intel each have one, and MSVC. That's a total of six compilers for the language.
Re:V8 adds support for top-level await by verisimilitudes on Tuesday September 24, 2019 @10:49PM (#21067119)
There are at least ten Free Software Common Lisp implementations and three proprietary ones that are still supported. There are four APL implementations, two of which are Free Software. As for Ada, I've read there are six Ada 2012 compilers, with GNAT being the only Free Software option; there are more, however, if you look to older Ada standards. It's interesting how these ostensibly more complex languages have compiler parity with C and C++ or exceed those, isn't it? I think the reason is that it's much harder to optimize C than it is more abstract languages, clearly, and C++ is simply so complex I've seen an entire company dedicated to just writing a parser for it.

As an aside, I find it interesting how many negative Internet points I'm receiving relative to the amount of responses there are. My figuring is many of these people can't argue with what I'm writing.

Re:V8 adds support for top-level await by LandR on Wednesday September 25, 2019 @04:28AM (#21068873)
Software development nowadays is as much about using trendy / hip tools than anything else, sadly.

Especially web development.

Re:V8 adds support for top-level await by duxup on Wednesday September 25, 2019 @12:04PM (#21072042)
Is it though?

The tooling is pretty wild and crazy but I see a lot of tools that really do solve problems.

Re:V8 adds support for top-level await by gridlockd on Tuesday September 24, 2019 @10:57PM (#21067161)
I'll tell you why these languages like Lisp or APL or Ada aren't actually good languages.

If they were really good languages, people would not only strongly advocate for them, they would give credibility to their advocacy by writing plenty of great programs in them, even on their own time, because they are very productive in these languages. They would also create best-in-class tooling to further their productivity even more.

What actually happens with these languages is that a certain breed of highly opinionated but highly unproductive programmer gets lost in them, on their quest for a level of aesthetics or elegance or correctness that nobody but themselves actually values. They don't build platforms for others to build on, they build pillars to prove a point.

Re:V8 adds support for top-level await by verisimilitudes on Wednesday September 25, 2019 @02:42AM (#21068318)
I disagree with your position that a language is good or not based on how people appreciate or use it. You're trying to tell me garbage such as PHP is one of the best languages there is, effectively.

In any case, I can still find examples of these platforms. Emacs is such a platform. For that matter, StumpWM is a platform of sorts. A nice quality of Lisp is making any program extensible with it trivially. As for Ada, governments use it for critical systems and I can't tell you the particulars of those. They use Ada because people will die if the software fails, so they're seeking quality, not quantity. You're advocating for quantity, not quality.

As for APL, you don't build platforms or such things in APL. If you don't see the beauty in APL, then that's your loss.

They don't build platforms for others to build on, they build pillars to prove a point.

First you claimed people don't build great programs in these languages, which I've shown is false, and now you're arguing that they build things, but not things someone can build on. You're conflating extensible software with quality software here.

What actually happens with these languages is that a certain breed of highly opinionated but highly unproductive programmer gets lost in them, on their quest for a level of aesthetics or elegance or correctness that nobody but themselves actually values.

That happens, less so with Ada, but why would that even be a bad thing? Is it wrong for someone to spend time working on what they view as an ideal and finished program, rather than just writing something that's already been done and requires little thought or effort on their part? It's infinitely harder, I think, to write novel programs than it is to simply implement some specification or write a copy or something else. That's what I do with my work. I don't see why you'd look down on that.

I'll let you've the last word, if you want it. I'm not interested in replying in this chain any further.

Re:V8 adds support for top-level await by gridlockd on Wednesday September 25, 2019 @09:00AM (#21070302)
> I disagree with your position that a language is good or not based on how people appreciate or use it.

This is unsurprising.

> You're trying to tell me garbage such as PHP is one of the best languages there is, effectively.

I wouldn't go that far with PHP, it's only used in a certain (lucrative) niche and I can't think of anything great written in it.

Python is a good language that grew organically much in the way that i described.

> As for APL, you don't build platforms or such things in APL. If you don't see the beauty in APL, then that's your loss.

It really isn't a net loss, because I consider the strive for beauty in a program a fool's errand. I get to write useful things in less time by foregoing that ideal.

> In any case, I can still find examples of these platforms. Emacs is such a platform.

Let's suppose that Emacs is a great program. Most languages that are not completely irrelevant have "that one thing" for people to point to. That's not enough. Furthermore, Emacs consists of about 25% of "ugly" C code.

> For that matter, StumpWM is a platform of sorts.

StumpWM? You're quickly running out of Steam here.

> First you claimed people don't build great programs in these languages, which I've shown is false

That's not my claim. My claim is "plenty of great programs" and "best-in-class tooling". A language like Lisp excludes best-in-class tooling or good performance by virtue of its design, so all programs that are written in it have a disadvantage right off the bat.

Ada had some potential here, but it got stuck. I suppose it had something to do with too much of it being proprietary solutions.

> You're conflating extensible software with quality software here.

I don't mean "platform" in the sense of "program that I can extend". I'm talking about the ecosystem and tooling surrounding the language, its "commons", if you will.

> Is it wrong for someone to spend time working on what they view as an ideal and finished program, rather than just writing something that's already been done and requires little thought or effort on their part?

First of all, this is a false dichotomy. Secondly, a program that is ideal and finished but less useful is a poor trade-off. A program that is less ideal and unfinished but has better functionality as a result is, in my view, a better program. More importantly though is not just the one program, but all the programs that can or can not exist, based on the trade-offs you chose.

If you just want to create pieces of art that the very few to appreciate, that's your prerogative. If instead you focus on usefulness however, you can create actual wealth, for yourself and for others.

> It's infinitely harder, I think, to write novel programs...

It is indeed, but it's much harder still to also make them ideal.

> That's what I do with my work. I don't see why you'd look down on that.

I don't look down on it. I just see nothing to look up to.

Re:V8 adds support for top-level await by masklinn on Wednesday September 25, 2019 @03:22AM (#21068538)
> All this async code without decent locking primitives is leading to a rabbit hole of race conditions...

That was an issue long before async/await was a thing.

Re:V8 adds support for top-level await by colordrops on Wednesday September 25, 2019 @02:32AM (#21068270)
Plenty of people replied to counter this misinformation, so why is it still at the top?
Re:V8 adds support for top-level await by pjmlp on Wednesday September 25, 2019 @02:52AM (#21068378)
Too many C devs upvoting it, I guess.
Re:V8 adds support for top-level await by namelosw on Tuesday September 24, 2019 @09:06PM (#21066545)
No, the abstraction is much better than C in 90s.

It comes from functional programming concepts (namely monadic design - which makes Haskell the 'best imperative language') which has some nice algebraic properties. It's easy to reason about, and don't really need lock machanism above the abstraction if properly used.

Re:V8 adds support for top-level await by pjmlp on Wednesday September 25, 2019 @02:51AM (#21068365)
Have C programmers already learned how to do memory management properly? Apparently 50 years have not been enough.

Don't assume all programming languages have the same design flaws as C.

Re:V8 adds support for top-level await by winrid on Tuesday September 24, 2019 @06:30PM (#21065338)
A good editor warns about some of those conditions at least.
Re:V8 adds support for top-level await by trust07007707 on Tuesday September 24, 2019 @07:23PM (#21065786)
It's best to use the phrase 'async control flow' instead of 'race conditions' when you're talking about single threaded execution.