A sync is a sync, until it's not

I've been writing Elixir at work for a few weeks. Yesterday I dove into a friends JS codebase and immediately got confused by the async stuff. While I used to write JS full time, the past 6 months I've spent in Elixir-land has really spoiled me.

I haven't written a lot of async/await code. It's supposed to be simpler than Promises which are also simpler than callback-soup.

Right.

It's a bit embarassing, but after all these years of writing Javascript code - I still trip over async stuff. Sometimes I'm hard on myself - mostly my interior dialogue thinks: You haven't written enough JS to have it be second nature. That might be true, but there might also be more to it.

Getting to know Joe

For the past few weeks, I've been starting my day with coffee and reading through Joe Armstrong's old blog. I thought to myself: If I'm going to be writing Elixir, surely I should get to know Joe a little bit better, (if all I can do that through is his blog).

A few days ago I stumbled across the post Red and Green Callbacks and I must say, it has made me feel a little less alone in how confusing JS can be. Let me quote a whole chunk of the article:


In Javascript you really don't want to do a blocking synchronous read in the main thread (and remember there is only one thread), so instead you have to setup a callback which triggers when the read completes. I call this a red callback. You write something like this:

var done  = function(x){ ... do something with x ..};
var error = function(x){ .... x ...}
read(Something, {onSuccess:done, onError:error});
 ...
 ... more code ...

Code like this melts my brain.

When the program is executing somewhere inside more code, the read completes, and I'm time warped back in time to the code in done, then back to wherever I came from in more code when done completes. I find this very difficult to understand.


Granted, if I understand the post correctly, Joe is mostly talking about the challenge of reasoning about callbacks, whereas I'm complaining about having to write it again, and being a bit confused about the syntax. The fact that Joe, the creator of a complex language and ecosystem has his brain melted by callbacks certainly makes me feel less alone about the whole thing. So, in the spirit of that, I might as well write something I don't understand at the moment.

Async to Async to Async

Here's an example that confuses me (and I could be missing something totally obvious). Note in the codeblock that comments denote the file name.

// api.js
async function deleteThing(thing) {
  // in the codebase I'm using this is a supabase call.
  return await callToDB(thing) 
}

// store.js
async function deleteWorkout(workout: Workout) {
  return await api.deleteWorkout(workout)
}


// my_component.js
// ...
async function handleDeleteWorkout() {
  const { error } = await store.deleteWorkout(props.workout)

  if (error !== null) {
    console.log("Failed to delete workout:",error);
  }
  navigate("/history");
  props.hideMenu();
}

//..
 return (
    <>
      <div>
        <div class="w-full pb-4 pt-2 px-4" onClick={handleDeleteWorkout} >
          <button>Delete</button>
        </div>
      </div>
    </>
  );

In the above code, working backward we have the following happen:

  1. User clicks component, an async function fires, which awaits store.deleteWorkout
  2. storeDeleteWorkout calls the api, awaiting it's function api.deleteWorkout
  3. api.deleteWorkout, when it resolves, returns an object with the shape of {error, data}.

What I find confusing is the train of async functions. If I'm understanding async/await, any function that calls an async function itself too has to be async. I guess that makes more sense when writing it out. But what if the code in the store needs to do something to the applications store data based on the result - the promise needs to be resolve then and there, and have the result do something to the store?

I guess it doesn't matter so much, because in this application we're using a reactive framework - and updating the store would just trickle the effects out to the children of the application.

In the land of elixirs...

I don't quite get what Joe means by Erlang having green callbacks, yet, but that might click in some time. All I know is that I don't have to write red callbacks in Elixir; that I can send messages to processes. I know there is a Task async / await functionality in Elixir, but I honestly won't really grok it until I use it (that's just how the ol' thinker works for me).

It's funny, I've been programming professionally for about 6 years now and concurrency/parallelism still confuses me. But that's also partially because the curious-cat in me wants to know more about what's happening behind the scenes; (ex: how do scheduler's work? How are .)

And no, I'm not asking you to explain it to me! I'll figure it out the way my mind was meant to figure it out in time. For me, that just means rinse and repeat, and a little bit of studying and writing blog posts when I feel like it :)