Let's start by simplifying the difference between sync and async code.
Synchronous means the code runs right now.
Asynchronous means the code will run later.
What about asynchronous code? When have we used that? A prime example is when our application needs user input to perform a desired behavior. When our application loads, we don't know when a user will click a button or fill out fields, but it will certainly happen later, not right now. In this situation, we use event listeners to wait and listen for an event that will happen later.
Imagine you throw a ball in the air, but you can only throw one at a time. This is the equivalent of single-threaded.
However, once you throw a ball in the air, you can throw a second ball before the first lands. That's the equivalent of non-blocking. If you didn't have this non-blocking capacity, however, you wouldn't be able to do anything else while a ball is in the air. You wouldn't be able to throw another ball or move around or do anything at all. In other words, you'd freeze up.
Ouch. Nobody likes when the browser freezes up. It's one of the most frustrating UI experiences a user can have.