Yeah, preemptive multitasking may be costly. But as you say, when you have lots of users, most of their tasks are sleeping or waiting for timers. That's where preemptive multitasking excels. You can have millions of sleeping tasks and several (at times) doing real work. That's why Erlang is "scalable" and node.js is not. Especially if you need to have state in your workers and if you need them to live longer.
Saying that node's cooperative multitasking excels in IO-bound computation is a clear sign that you haven't tried Erlang.
I have another counter-point for this as a rails developer tinkering with golang recently. I think Go got this correct in many ways. Having light weight goroutines that can scale well; having good IO which does epoll/libuv style wait in the background transparently when you read/write. Easy to understand multiprocessing language in general. I have no idea why it's not taking off in web development though.
Wat?!
By the end of the 90's, people started the "10K project", aimed at adjusting Linux and Apache so that a hight-end machine could support 10k simultaneous Apache threads, all doing IO at the same time. And they were successfull.
That was more than 14 years ago.
Have you used Async.JS? In my experience, it has always solved my deep callback problems.
Doing CPU-bound computation in your application server is an anti-pattern. IO-bound computation, however, is where Node excels.
The thing to realize is that pre-emptive multitasking is costly. It is convenient for the programmer (the programmer doesn't need to worry about blocking and locking up the rest of the program), but it comes at a cost. Lightweight "green" threads, or equivalently, Node's evented dispatch mechanism, are a much more efficient use of the CPU. For applications composed of short-lived computations (e.g., < 1ms), it doesn't make sense to interrupt them and context switch. It's more performant to let them complete and then switch. You just have to make sure you aren't doing any CPU-intensive computations in the app server—which you probably shouldn't be doing anyway.
The downside of Node's approach is callback hell. And that's why we have Go.