Let's see the forest first before looking at the trees.
There are many informative answers with great details here, I won't repeat any of them. The key to programming in JavaScript is having first the correct mental model of overall execution.
- Your entry point(s) is executed as the result of an event. Forexample, a script tag with code is loaded into the browser.(Accordingly, this is why you may need to be concerned with thereadiness of the page to run your code if it requires DOM elementsto be constructed first, etc.)
- Your code executes to completion--however many asynchronous calls itmakes--without executing any of your callbacks, including XHRrequests, set timeouts, DOM event handlers, etc. Each of those callbacks waiting to be executed will sit in a queue, waiting their turn to be run after other events that fired have all finished execution.
- Each individual callback to an XHR request, set timeout or DOMthe event once invoked will then run to completion.
The good news is that if you understand this point well, you will never have to worry about race conditions. You should first and foremost thing of how you want to organize your code as essentially the response to different discrete events, and how you want to thread them together into a logical sequence. You can use promises or higher level new async/await as tools to that end, or you can roll your own.
But you shouldn't use any tactical tools to solve a problem until you are comfortable with the actual problem domain. Draw a map of these dependencies to know what needs to run when. Attempting an ad-hoc approach to all these callbacks is just not going to serve you well.