Ever wondered how JavaScript, a single-threaded language, manages to pull off asynchronous tasks? In this blog post, we’ll break down JavaScript’s asynchronicity using a playful analogy. Discover how the 🧵 waitperson seamlessly coordinates with a ⚙️ kitchen, 👩🍳 kitchen assistants, and a 🛎 counter to serve your requests while keeping the show running smoothly. Discover the secrets behind steps that can happen in parallel and the importance of not giving the 🧵 waitperson long tasks. Ever wondered why some websites freeze? Find out here! 🤓
While I could delve into the technical details of JavaScript's single-threaded asynchronicity, I believe there is already a highly informative and widely shared technical explanation available on the topic. In this case, I am referring to the very good explanation by Philip Roberts which you can find directly after this line 👉
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/8aGhZQkoFbQ?si=xL4CRiLd37b5uM8d" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
Therefore, I will try to introduce the topic in a more accessible manner that anyone, regardless of technical knowledge, can understand.
The Situation
JavaScript (JS) is a single-threaded language, which means that it operates like an individual doing a job, as opposed to a multi-threaded language which operates like a team doing a job. As a single thread, it cannot work on anything other than the current task.
Despite this limitation, we can achieve asynchronous behavior when working with JavaScript. For example, we can make multiple requests for information simultaneously without having to wait for each one to finish before making the subsequent request.
JavaScript is a single-threaded language. So how is it asynchronous?
The Analogy
ℹ️ This post is targeted at how JavaScript runs in the browser, however,
many of these concepts translate to other runtime environments (e.g.
Node.js).
To simplify the different elements that take part in the analogy, I will gradually introduce them along the way. As I introduce the elements, I will leave callback blocks as quick references in case you need to refer back to something.
The setup
Let’s imagine we have a 🛖 restaurant with only one 🧵waitperson.
The 🧵waitperson is our interface to ask questions, get answers, and perform actions.
🧵 waitperson = main JS thread
The 🧵waitperson can only attend to one 📝order at a time. However, getting the client’s order does NOT occur sequentially. Instead, everyone’s food goes through the same process of ordering, preparing, and serving at the “same time”. But there is only a single 🧵waitperson, so how do they manage it? 🤔
📝 order = JS command
The flow
In this case, the 🛖restaurant has more going on than the customers get to see. For example, it needs a ⚙️kitchen to work. The customer usually does not know what is happening inside the ⚙️kitchen, yet it handles much of the work required to deliver what they ordered. So, what is the flow here, and, once again, how does one 🧵waitperson manage to serve all the 📝orders concurrently?
⚙️ kitchen = Browser
Let’s break it down:
- A customer makes an 📝order; during this time, the 🧵waitperson is blocked and cannot answer anyone else or do anything else during this time.
- Once that is finished, the 🧵waitperson either immediately fulfills the request(e.g. the customer asks for a straw) or takes the 📝order to the ⚙️kitchen.
- The ⚙️kitchen prepares the 📝order using 🔪kitchen utensils and tools.
- Once the 📝order is done, ⚙t️he kitchen puts it on the 🛎️counter.
- Eventually, when the 🧵waitperson is free, the 🧑🍳kitchen assistant (who is needed in this case to coordinate between the busy 🧵waitperson and ⚙️kitchen) picks up the order from the 🛎️counter and gives it to the 🧵waitperson.
- The 🧵waitperson then delivers it to the customers.
- 🔪 kitchen utensils and tools = Web APIs
- 🧑🍳 kitchen assistant = The event loop
- 🛎️ counter = The task queue
In conclusion
The beauty of this is that during steps 3 and 4, the 🧵waitperson is free and can do other things. So, the 🧵waitperson returns to other customers and gets more 📝orders, and the process repeats.
Steps 1, 2, 5, and 6 can ONLY be done sequentially; in other words, the waiter cannot take two tables’ 📝orders simultaneously. However, steps 3 and 4 can happen in parallel, the ⚙️kitchen can and will cook as many 📝orders as possible.
Interestingly enough, the 🧵waitperson steps tend to be quick, and the ⚙️kitchen steps tend to be slow. Still, the ⚙️kitchen has more than one person working on the orders, so everything works smoothly and seems to happen fully in parallel.
Note that it is critically important that you do not give long tasks to the 🧵waitperson; If you do, for example, ask the 🧵waitperson to cook, then they will be blocked in cooking while customers are waiting to place their 📝orders. No one answers their calls and rings, making it seem like the 🛖restaurant is frozen.
I guess now we know why websites that use JavaScript freeze sometimes 🤓
I hope this analogy helps you. If it does, please reach out. We want to know about it! If it did not, or something is incorrect, let us know; we are always happy to learn and improve.
Kudos/special thanks to Sean for bringing up the challenge, for encouraging my analogy to become a blog post, and the foobar Agency team, who supported me along the way with guidance and thorough reviews.