I'm one of the NodeJS developers at Computer Know How. I've had the privilege of being on a number of scaling NodeJS API projects in recent years. I have become quite familiar with, if not comfortable with managing JavaScript in an asynchronous environment.

Intro to JavaScript & Asynchronous Programming

JavaScript is my favorite language to program in personally. I've invested a lot of time learning the language itself rather than either using it when I have to or learning just the pieces that I need for a project. It's quirky, imperfect, and sometimes very frustrating. I like that. Reminds me of myself in some ways.

I program a lot of APIs. Asynchronous programming is one of the basic concepts of programming APIs, like DOM manipulation in the browser. When programming APIs, one has to get used to interacting with architectural pieces or 3rd party resources outside of the core program. Engaging with the asynchronous nature of APIs can be very frustrating, but it's the part that makes a program relevant. Connecting with elements outside of itself is at the heart of what APIs are designed to do.

JavaScript APIs have a particular complication when engaging with asynchronous programming. JavaScript itself is not designed for asynchronous "out of the box" because of the nature of the language itself. The code that we write is not processed in the same way that it is written. For example, consider JavaScript's hoisting aspect: "variable and function declarations are put into memory during the compile phase" in a process called hoisting. Therefore, they are available before they are physically declared in the code that we write. Another aspect is that JavaScript doesn't recognize a stop or wait mechanism internally during code execution. If a process is reaching out somewhere else in the world, putting it out-of-sync (asynchronous) with the code that is being processed, the processing code will simply move on. The first time a developer experiences this is frustrating and feels completely unnatural. Especially if that developer has experienced the good pleasure of working with languages that have such an internal stop or wait mechanism and process sequentially. This hyperactive nature of JavaScript execution is both useful (program efficiency) and problematic (asynchronous issues), the proverbial double edged sword.

Asynchronous Solutions

Since EcmaScript 6 has become adopted by browsers and NodeJS alike, and some of the parts of EcmaScript 7 have been pre-implemented, there are three viable solutions for handling asynchronous functionality with JavaScript: Callbacks, Promises, and Async/await.

Callbacks are the first and basically original solution for JavaScript asynchronous management. They utilize the core language's versatile functional parameter rules and the encapsulating nature of functions themselves. A Callback is essentially a function that is passed as a parameter to another function. The Callback is then triggered by the code within the encapsulating function.

Promises are a solution that was requested from the greater JavaScript community after Callbacks had been in use for a few years. Many members of the community did not appreciate the syntax, pitfalls, and complications that came with Callbacks, so they asked for a more straightforward solution to work with. The first solution to that problem became known as Promises. A Promise is a special type of object that will resolve with a value (yay!) or reject with an error (boo!). There are few syntactical options for using Promises, the most basic of which is to return a Promise object as the context of a function. The key way of executing Promises is in a "thenable" context. A thenable context is a function declaration that follows with a .then() (which processes the resolve option) and a .catch() (which resolves the reject option)

The third solution is part of the continuing evolution of Promises. Just like Callbacks, Promises had complications in their implementation. Most notably, they were extremely verbose to implement from the calling side. So, the greater JavaScript community wanted something simpler. They somewhat got that with Async/await. Async/await is syntactic sugar built on top of Promises that simplifies the usage of Promises from the calling side. Probably the most well-received part of the Async/await specification is the await keyword. In an async context, the developer has the ability to use the await keyword to pause code execution, temporarily stopping the hyperactive nature of JavaScript and forcing it to process in order. A key drawback to Async/await is that it is not a completely independent solution, it's built on top of and therefore dependent on Promises. Its design is to simplify the usage of Promises from the calling side.

Work in Progress

None of these solutions are perfect. The syntax of Callbacks is complicated. The verbosity of Promises is cumbersome. Async/await is not an independent solution.
Because none of these are clearly perfect, I have a feeling that we are not finished finding solutions in the JavaScript community for asynchronous development.
The good news is that no matter what your preference, you have options. And good options at that. Learning to be comfortable with these solutions and using the best one for your program's context is the key to being proficient at asynchronous programming with JavaScript.