You've basically been lied to about "async" in the past few months, or at least led on by ignorant people; the word used to mean something but there has been a concerted effort to screw up its meaning. When you write in Haskell

line1 <- hGetLine handle
line2 <- hGetLine handle
line3 <- hGetLine handle

(Which is not quite the exact same thing shown, I'm showing an example here.)

This will get three lines. These lines may not come right away; someone could be using Telnet to send them for debugging purposes, for instance. The Haskell scheduler automatically notices the socket is blocked and puts the thread away until there is enough input to finish the line. It may even end up reading the line from many packets. Once it is ready, control returns to your code, then it may do it again for line two. At no point will the main OS process be "blocked"; you can have a ton of these running simultaneously, all waiting at different points. The language and runtime simply take it in stride.

That you may have to manually chop code up into a series of callbacks because of the limitations of various technology stacks is one thing. I've had to do that too. The way this has been promoted in some circles as the only right way to do it is absolutely bizarre. No, it's not right. You should be able to just write your code and not be responsible for manually scheduling it. Compilers and the runtime can do that for you, and it's actually easier for them.

The lie I referred to is that the source code I show must be synchronous because you don't see the points where it is chopped into little bits. The truth is that this is perfectly possible to do automatically in any number of ways, and that code is at least as "asynchronous" as Node.js code (and possibly more so, depending on the implementation of "get one line"). This isn't a special Haskell feature, either, numerous other good languages can do this, Erlang, Go (I'm pretty sure), Stackless Python, Lua (I think), and that's not even a complete list.

Furthermore, it's not only asynchronous, it's a superior type of asynchronous. This code maintains execution context way better than any manually chopped up "asynchronous" code could. If the code I showed was in the local equivalent of a "try" block, the exception would actually work as you expect; whether the first, second, or third call fails, they could all be handled by the same try block, exactly as the source code would show. If I got a stack trace (in one of the other languages I mentioned where "stack trace" means something, in Haskell it's a bit... trickier), it would be completely sensible. The composability of code written this way is far, far superior to manually-chopped up "asynchronous" code, because functions can simply take other functions as arguments without having to care whether those functions themselves may have asynchronous components to them.

Fun task, try converting the following psuedocode into Node.js losslessly, with only one "try" and no duplication:

function readTwoLines():
return [readLine(), readLine()]
function readLineAndDie():
readLine()
throw NoLines("No more lines, I think")

function doThings([functionList]):
# 'call' just applies the function
try:
return map(callFunc, functionList)
catch NoLines:
return null

function main():
a = doThings([readTwoLines, readTwoLines])
b = doThings([readTwoLines, readLineAndDie])
print a, b

That's should consume 7 lines, and print a list containing a list of the first two lines, then a list of the third and forth lines, then print a null. It may be possible, but you won't like it.