Another approach to async IO is CPS (continuations passing style), in which you write imperative style code. This imperative style code is then compiled such that the blocking IO operations are called with callbacks, which are the remainder of that block of code (the continuation) - allowing the calling thread to be re-used while blocking for IO. Relies on the continuation having access to the outer/parent/previous-part function via closures.
It'll be interesting to see if people start doing this. Requires people to understand continuations and closures (which more people have exposure to now via JavaScript), and library support.