Async behavior changed in 0.2.0+ #256

Closed
tsgautier opened this Issue Mar 4, 2013 · 1 comment

Projects

None yet

3 participants

@tsgautier

Well, I just ran into an ever so subtle "undocumented" feature of async. Took me a few hours to unwind this one.

Async 0.1.22 used to just iterate over your series or parallel methods "forever".

In 0.2.0 this behavior changed, and instead, at each step, nextTick is called-- so instead of synchronously executing your methods on the stack, they are queue'd up in node's event loop.

In practice, this doesn't change the external contract at all, but it does change the way your call stack works.

For my actual application, this timing difference makes no difference, so generally I would say it's a good change, because it allows a long sequence of serial steps to "yield" to other things.

The problem for me happened in my unit tests. I use nodemock for my unit tests. I don't think there's any getting around the asynchronous nature of js tests, so probably it doesn't matter what you use, you'll have this problem in some way or another.

The problem is this: to test asynchronous code, it usually goes a little like this:

someTrigger = {}
setupTheAsyncMock(describe the call that will be made, someTrigger)
callTheCodeUnderTest()
someTrigger.trigger(some value that we are passing back to the async callback)

In this way, the test is "driving" the asynchronous behavior, basically when the code under test makes the async call, the stack 'unwinds' and returns to the caller (the test in this case). It's just "waiting" for the async to callback the closure that was passed. Now the calling code (the test) calls trigger which is just a fancy way of executing the callback that was passed in when the async method was called from the code under test.

This all used to work fine, until async 0.2.0. The problem is, with a bunch of steps, the code "unwinds" back to the caller too early, because async instead of looping around, has called nextTick so it unwinds immediately and the async thing that would have happened synchronously now happens on the "next tick" of the event loop.

Since the caller code is ready to go, it calls the trigger, but the "next step" is queued up in the nextTick, and so the async method we are expecting to call hasn't been called, and thus there's nothing to trigger.

I can see two solutions to this - one would be to mock out nextTick. That seems - not fun. The other would be to provide a flag to async so that it works synchronously instead of using nextTick to facilitate tests.

@brianmaissy
Contributor

If I understand your issue correctly, this is a duplicate of #243.

I agree that mocking out nextTick is about as bad a solution as could be had. But I don't know if changing async to have a synchronous mode makes much sense either.

I think the proper way to fix your test is to pass a continuation callback to callTheCodeUnderTest() which will call it when it finishes all the stuff it's supposed to do. I understand that it's not asynchronous, but it can still use the callback style, since apparently the code you're using async for is also not asynchronous but uses the callback style anyway.

@caolan caolan closed this Mar 28, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment