The curse of the never-ending async method call

Whether you use promises, callbacks, or other abstractions (except maybe the async/await pattern) to deal with asynchrony, control flow is going to get harder to follow. Stack traces are typically meaningless, and finding what went wrong can take substantially more time. One thing that very often goes wrong, especially during development, is when an asynchronous method never calls back. It may be because a service never answers, or because there’s a bug in the code that causes the callback to never be reached. No matter what the cause is, when that happens, you’re typically out of luck because by the time you notice the unusual delay, the execution engine has already left your code without leaving a trace. Breaking into the debugger will just lead you nowhere. You’ll have to tediously re-run and trace the execution of the code with lots of breakpoint into lots of callback functions. No fun.

To mitigate and avoid the problem, there are a few simple things you can do:

  1. Bake a time-out into the application: this won’t help that much to find what the issue is, but at least the client won’t be faced with a never-responding blank screen. You can also add code to log the error, recycle the process, etc.
  2. Test asynchronous code even more than you usually would. That will deal with lots of the code flow issues.
    For example, avoid test callbacks that immediately call the next function:
    somethingAsyncThatImTesting(
    someArray,
    function someIterator(item, next) {
    // check something about item
    next();
    },
    function iterationDone(err) {
    // check some more stuff, then end
    }
    );
    This will hide some subtle issues because it’s not really asynchronous, it just has an asynchronous signature. Make it really asynchronous, like this:
  3. somethingAsyncThatImTesting(
    someArray,
    function someIterator(item, next) {
    process.nextTick(function() {
    // check something about item
    next();
    });
    },
    function iterationDone(err) {
    // check some more stuff, then end
    }
    );

What did I miss? What do you do to mitigate such issues?

No Comments