Javscript is affected by the need to be sometimes async and sometimes sync. For me to make network calls and updates, it needs to use async code.
This means I cannot do something like this. It won’t work, because I’m trying to call asynchronous code from a sync loop.
myList.map(entry => { await fetch("${entry.url}")})
So, looking around for solutions, I discovered there was a variety of approaches to do this.
await Promise.all( myList.map(async (entry) => {})
for (const entry of myList) {}
I finally went with Promise.all
, naively thinking this was a semantics issue.
Just recently, I had code that effectively did the following:
- Get a list of items to log about.
- Go through the list and do an async log update to one record.
Not too difficult, I really just expected it to look like this after completing the list:
[ { "message": "Log #1" }, { "message": "Log #2" }, { ... }]
The problem, is, the log actually just looked like this:
[ { "message": "Log #1" }]
This wasn’t possible, because there were several dozen that it needed to do! It also started to enter into “magical” territory, where things were starting to feel impossible.
My first test was to do a finale check. After it finished the list, it should say “I’m done!”. I predicted this also wouldn’t show up, because there must be an error preventing the subsequent logs to show up.
Alas, I was wrong. It did show up. Where did the other 11 logs go?
[ { "message": "Log #1" }, { "message": "I'm done!" }]
My next (desperate) step was to do a console.log
next to the network call, then do another console.log
later on in the loop.
Was it running the code at all?
Turns out, it was.
logging #1 logging #2 logging #3 completed #1 completed #2 completed #3
[ { "message": "Log #1" }, { "message": "I'm done!" }]
You might spot the problem, and if you have, you’re smarter than me, because I looked at that and thought “Ah, ok, everything is fine, it is doing the code. Now where could the problem be…” After struggling for a little while further to debug this, putting in try catch blocks, I finally looked at the log again and thought
That’s Weird.
Yeah, the console log was starting all 3, then finishing all 3. In other words, concurrently, not consecutively. At that moment, the thought ran through my head, “It’s not doing a race condition for these logging attempts.. is it?”
And it was.
I jumped into Kagi search, looked up the issue and discovered there’s a lot of problems in this area.
Promise.all race conditions
Turns out, Promise.all
works like this.
myList.map(entry => async () => { console.log(e) }
generates a list of Promises. Promise.all
then just calls them all
at the same time, so they’re all running concurrently. When they’re all done, it moves on.
This is great if they’re all truly independent. But my code was wildly not independent, they were consecutive logs to show progress!
To resolve this, I switched the code to still allow async but do it item by item, consecutively:
for (const entry of myList) { await myNetworkCall()}
Suddenly, everything was solved and working again. And I learned the difference between Promise.all
and doing async code
inside a javascript for loop. I guess it wasn’t just semantics after all. Got me again, Javascript ><