Then, the event loop removes the oldest message from the queue and executes its associated handler function. This function may call on other functions to help it do its business. While this chain of functions is being executed to completion, no other handler function is. Since, of course, the event loop runs in a single thread. You can read all about it on MDN.
But, there is a problem with this well-known model.
If one chain of functions needs more than one iteration of the event loop then someone else might mess with its business in between. Every async function in the chain is an opportunity for the event loop to pick out and execute other handler functions. If two such chains of functions (processes if you will) share the same data then you might have problems you’d think you would not encounter in JS. The elephant(s) is, as you may have guessed by now, concurrency problems.
A concurrency problem usually causes data inconsistency or data corruption, and it can sometimes hurt performance. It manifests itself as the Schrödinger bug, it always disappears just when you try to look at it. So, a concurrency problem is hard to track down and fix.
I don’t have a good example of a concurrency problem in client-side JS and I don’t want to insult you with a contrived one. Instead, allow me to indulge in a little speculation. When Facebook introduced the Flux architecture they spoke about a bug in the unread messages count. Now, remember this is just a speculation, could it be that they had a concurrency problem?
Server-side JS has more opportunities for concurrency problems. In one part of a system I’m working on, I needed to restrict the number of concurrent chrome instances a nodejs service spawns. For each request, the nodejs service uses a chrome instance to render a web page, scrape some data, take a screenshot and upload it to S3.
The calls to chrome are async (via puppeteer) allowing the nodejs runtime to happily service other requests instead of blocking. The need to restrict the number of concurrent chrome instances arises since using chrome to render a web page takes a lot of resources (memory and CPU). So, if you don’t restrict the number of concurrent chrome instances then performance would be severely reduced (due to context switching and thrashing). The solution, in this case, was to maintain a shared count of the active chrome instances and block requests if it’s above a configured value. (The semaphore implementation from async-mutex came in handy.)
You may have already encountered and solved concurrency problems in JS. Perhaps you even didn’t realize them as such and now think you wasted your time reading this post. Granted, it’s relatively easy to solve concurrency problems in JS. But, if you name the problem correctly you are in a better position to solve it well. At the very least you can save time and effort by using existing solutions.
The solution to concurrency problems involves safely synchronizing processes. You have to make sure that when each one is accessing a shared resource no other is allowed to. You do it by using synchronization primitives. The most common of them all is the mutex.
Please understand, my descriptions in this post lack in detail since 1) you can find it all at other places and 2) thinking about writing all the details made me procrastinate on this post for several weeks now. There is no shortage of material on this topic, provided you are not allergic to other programming languages. If and when you go to find out about those details keep in mind two points.
The first point is that there are several synchronization primitives you can use to guarantee safe synchronization. Some synchronization primitives are more effective than others in certain situations. So, it’s wise to have at least an idea of the different available tools rather than relying on your favorite hammer.
The second point is that using synchronization primitives is only part of the solution. You still have to make sure that there are no deadlocks, livelocks, or starvation in the system and that the critical sections are minimal in size and number. Using synchronization primitives get you the correct syntax, make sure to get the correct semantics too. Sadly, many blog posts I’ve seen cover only the basic part of using synchronization primitives.