Q:

How to debug timing issues - sequential AJAX requests, async chains, etc

Suppose you're new to a project that uses a lot of AJAX requests and other asynchronous methods (e.g. Observable subscriptions). If you're seeing an intermittent issue, and you suspect the problem is due to the timing of AJAX responses and resulting observable updates, how would you even begin debugging this?
Here's what I've done. First, I drew sequence diagrams for the relevant code. Then I tried to imagine scenarios that would cause problems, like out-of-order AJAX responses. This takes a lot of time, so instead I often try introducing delays into various REST endpoints in an attempt to expose the problem consistently. Another shotgun-debugging technique I've tried is logging all events and observable updates, but this is way too verbose to be practical.
Is there a better way? It's hard to visualize all of the complexity, and coming up with potential causes just seems like a shot in the dark.

A:

Simply put, you HAVE to document all your async dependencies and then you have to code to make absolutely sure that regardless of timing, all your dependencies are fulfilled.  This is the place to start.  If this information isn't 100% known, then you can't even begin to ensure the design is correct.
So, this means figuring out exactly which operations are independent and can run on their own and complete whenever and which operations have dependent async operations that must complete first.  For the operations which have dependencies, you MUST make sure the code enforces that dependency.  This isn't the step where you imagine out of sequence operations or weird timing.  This is the step where you fully understand and document for every async operation what has to complete before this operation can either be started or before the result can be processed.
Promises are often a nice architecture for both stating dependencies in the code and then enforcing that dependency.
So, if a and b must complete before c runs, then you have to code such that your code enforces that dependency.

I would NOT suggest that you just try to test the heck out of it to "find" all the problems.  Not only is that hard to do, but it isn't really the structured way to make sure you understand the design requirements and that you have code designed to implement those design requirements.  
Once you think you fully understand what is required and think you have implemented it, then and only then is the time to start figuring out if you can test what you've built to see if it reacts the way you expect and also as a check to see if you've missed any dependencies.  This is the step where you start figuring out how to create out of sequence results in order to test your dependencies.  But, you're only doing this to test if the dependencies you've already identified and coded for are working and implemented properly, not to find new things to design for.

If you have a project with a lot of async operations, I would strongly suggest that you use some sort of library to help you manage those async operations and dependencies between them.  Depending upon what else you're using in your project and whether it's client or server, you have many different choices.  The whole promise architecture looks like it's one of the main design directions this type of thing is headed in the future - ES6 will have promises built-in, many libraries like jQuery have some sort of promises implementation built in, etc...  Promises allow you to either sequence async operations or declare that multiple async operations must complete before starting the next one and so on.

One more thing.  Once you've identified a problem and need help debugging it, you often cannot use breakpoints in the code because they tend to just mess up the timing of everything that comes afterwards which totally changes the very situation you're trying to look at (kind of like a Heisenberg uncertainty principle for async debugging).  As such, I tend to resort to detailed logging so once you see a problem, you can go back and study the logs and see what actually happened.  With a more detailed understanding of what might have happened, you can then often either add more targeted logging or perhaps then set a breakpoint to examine the issue or maybe by then, you'll see what dependency in the code you don't have working right or missed that you had to code for.

