8 Comments
Oct 2·edited Oct 2Liked by Zach Daniel

I think your JavaScript example on concurrent mutation is wrong. Because `doSomethingElse` is not an async function or promise you await, it doesn't matter how long it takes. `newScore` cannot progress past its own await of the timeout while the code that launched it is still running. So the value of `profile.score` at the end is always `3`.

The code would have the behavior you describe if it read `await doSomethingElse();`.

Expand full comment
author

fixed

Expand full comment
author

I haven't done concurrent js in a hot minute, but that would be pretty surprising to me. we call `newScore`, which is an async function, so that should be running concurrently with the subsequent code, no? What is the point of using `async` then?

Expand full comment
Oct 2Liked by Zach Daniel

JavaScript is inherently single-threaded and execution is not preempted. At the core is an event loop running functions in response to events. There is also a "microtask queue" of functions that get run just before returning to the loop.

Async functions are a rather complex beast made to appear to be concurrent when all you really have are events and deferred tasks and callbacks, not threads and preemption.

In an async function, awaiting a Promise attaches callbacks to resume the async function after the await point (with the promised value if the Promise was fulfilled, or by raising an exception if the Promise was rejected). The async function then returns. If this is the first time the async function returns, this also returns a pending Promise to the caller.

A return statement in the async function is really a call to resolve the Promise it returns. Similarly, an exception from the function is transformed into a rejection of the Promise.

In the special case of an async function with zero await points, the returned promise is already settled (either fulfilled or rejected). When a Promise is settled its callbacks do not get run immediately but in a microtask, so an await always returns and never immediately resumes the async function.

So in your example, when you run `newScore`, it sets up the timeout and returns a pending Promise. It will not progress until the timeout event resolves its promise, which cannot happen until the surrounding code has returned to the event loop.

Expand full comment
Sep 28Liked by Zach Daniel

Nice write up! There's a couple bugs in the Scores module though... a lowercase "p" in `Process.get` and the `score` var isn't initialized. But it's a nice topic to ponder and its great to share this information with folks new to Elixir. The power of the first-class, baked-in Process in BEAM languages is awesome. Ironically though, even though I've written lots of GenServers I haven't yet had a need to reach for the Process dictionary. I suppose it's just not the sort of problems I've worked on. I try to stick with just mutating pipes, from input to result, and avoid in-memory global state. One of these days, I'm sure I'll have to. Cheers!

Expand full comment
author
Sep 28·edited Sep 28Author

Will fix the example! As for not using process dictionary, you may not use it personally, but a lot of tools might it on your behalf, for things like tracing etc. it’s definitely something that there is no good reason to use when writing our day-to-day code.

Expand full comment

What colour theme and fonts are you using in the screenshots please? :)

Expand full comment
author

It is synthwave 84 on https://carbon.now.sh

Expand full comment