It's worse than that: it's true that in-process asynchronicity is a performance optimization, but it doesn't follow at all that the code needs to read asynchronously.
There is no performance reason to make developers hand-code in the continuation-passing style. That should be the job of a compiler or runtime environment.
Erlang does it. Stackless Python does it. Go does it.
The whole "look how fast Node is due to all my hand-rolled callbacks" is a total fallacy. It's a case of taking perverse pride in an accidental wart of your language.
Slowly the node community is maturing and beginning to realize that if they want to build robust, fault-tolerant software they need better primitives than hand-rolled callbacks. Hence the slowly rising mindshare of Promises, which are strictly better, but would be better still if the language itself baked them in implicitly (consider that nearly any value in a Haskell program is represented by a promise under the hood, but you never have to deal with them explicitly).
There is no performance reason to make developers hand-code in the continuation-passing style.
node certainly recognizes this. However, there is no need to do anything here since JS is taking care of this with ES6. Even if you don't want to use ES6, there are now multiple ways to compile it down to ES5.
Hence the slowly rising mindshare of Promises
Promises are declining, and honestly doesn't really solve the problem in most cases. A couple of years from now, you won't see promises on the server. You should see tjholowaychuk/visionmedia's co[1]; that's how JS is going to look like in future.
In terms of expressiveness, there isn't much to choose between dynamic languages. While it took some getting used to, programming JS wasn't that different from writing Python. However, JS/node is succeeding because this is the first time ever that we have a language/platform that is truly write-once, run-anywhere. A significant advantage in favor of node.
Yes, I'm looking forward to ES6. And I agree that Node's strength is that Javascript is truly the lingua franca of the modern web, which I why I would even bother using it.
But the value of running everywhere is also why ES6 is not a panacea. As you say, soon we won't need promises "on the server". But I want my codebase to run correctly everywhere. Promises are a bandaid that I'll need for quite a while yet.
Something silly is like putting shit-tons of fish into a box and expecting it to make HTTP requests. Writing a web server in PHP, regardless of what you think of the language, isn't. Comments like that are just gonna clutter someone's Google search one day.
If all you have is a room full of PHP developers and all you need is a REST site, then well you could do worse but I would like to discourage you from the practice.
Similarly, unless you have a highly specialized need - fun asynch chat server or complex app where business logic is being directly shared between client and server - I too would discourage locking one's self into that platform.
Javascript is not the worst thing ever, but why bother if you don't have to?
1) Laravel is a fantastic choice for building web servers if your team is primarily PHP. 2) There is no such thing as locking yourself into the JS platform, because JS has the advantage of being our lingua francua, is a general purpose programming language (servers, clients, and more!) AND is the language with the most momentum TODAY. Not last year, not 20 years ago.
Promises are declining, and honestly doesn't really solve the problem in most cases. A couple of years from now, you won't see promises on the server. You should see tjholowaychuk/visionmedia's co[1]; that's how JS is going to look like in future.
I'm impressed that you apparently completely failed to realise co is an abstractive layer over promises, something it states upfront:
Generator based flow-control goodness for nodejs (and soon the browser), using thunks or promises
What you yield from your generator is not magical pixie dusts, it's promises.
The generator is the driver, but the driver on its own its useless if it's got nothing to drive.
And what it has to drive is promises (or thunks since most existing node code is thunks-based I guess it makes sense to support that. taskjs uses promises more or less exclusively for the same purpose, Python 3.4's asyncio uses behaving much like JS promises, etc...)
Typically in apps using 'co', functions will return generators, not promises. Promises were probably added because of all the existing code out there.
Again, generators in and of themselves don't do anything (for async code). Something needs to be generated/yielded, and that thing is the reification of an asynchronous operation. Such as a promise[0]. Generators and promises fulfil different roles, generators are used as imperative "sugar" for chaining promises.
The generators you yield will ultimately resolve into a series of async operations (promises or "thunks"), yielding a generator is convenience (not very useful convenience either, since `yield*` exists the ability to yield generators just complexifies the driver)
[0] and in the case of co a "thunk"[1], because node
You can also use plain functions, objects, array, and even other generators too. Least, that's what the framework tells me when I fuck up my tests. It's not just all promises under the hood.
First: reactor-based event loops aren't always a performance optimization. Round tripping I/O operations through an event loop adds latency. Node's approach is great if you have large numbers of mostly idle connections, such as in a chat or websocket server, but slower than a multithreaded, blocking approach when you have small numbers of active connections. What you really want is a system that supports both models, so you can choose the best one for your problem.
Some Node developers, for example the ones behind Meteor, have come to the conclusion that callback/promise-driven development is painful and tedious, and started wrapping up the asynchronous behavior in coroutines.
Unfortunately, to participate in this sort of world, where you wrap the asynchronous spaghetti into what the "Node.js Is Badass Rockstar Tech" video would call "sequential code, you know, the code you can read", each async library must be hand-plummed into a synchronous version that does the coroutine juggling.
This puts Node in exactly the situation it was trying to escape: now there are two different I/O models, one synchronous, one asynchronous, and not all libraries support the synchronous model, so by trying to leverage it, you're cutting yourself out of large parts of the Node ecosystem.
Better environments abstract over the I/O scheduling, letting you choose between threaded, blocking I/O, and a M:N task scheduler which can handle many lightweight tasks being scheduled on native threads. Rust comes to mind.
It was about how essentially, in a large concurrent system, a chain of callbacks like:
cb1 -> cb2 -> cb3|eb3 then cb3->cb4 and eb3->cb5
(select/epoll returns and calls cb1, chain ends with cb4 or cb5 depending if errback eb3 is called).
Is just a messy, dangerous and confusing re-implementation of threads/goroutines/tasks. Besides spreading the business logic among multiple io related function or sprinkling yields() or thens() it doesn't completely save one from needing locks and semaphores if shared or non-local data is modified.
A second callback chain from cb1 could have started before the previous one finished. Now they both could be modifying the same data. Yes granularity level in this is at code block level between IO points not assembly instruction, as the system grows large this problems becomes apparent.
I had to deal with it in a Python Twisted based framework. There is Twisted Semaphore and I had to use it.
The node.js and reactor-based event loops look _very_ nice in small demos and when the callback chain is shallow. HAproxy or nginx are good examples of this. They have shallow callback chain. Node.js demo example also look good, "Oh look you can serve 'Hello World' in 5 lines on a websocket!'" stuff like that.
As systems grow larger, callbacks chains as concurrency mechanisms start to suck.
It's all async under the hood because, in the end, you can only really resolve one thing at a time. Just because generators and promises let you write a more synchronous style of programming, essentially sugar, it doesn't mean that it's actually working in a true sync fashion.
When people start describing how Node works rather than bloviating blind praise, the first thought I have is, Windows. Yeah thats how windows works too. Im totally sold now!
Using synchronous I/O is only simpler if you never have to synchronize threads, which you always do.
After taking a closer look at Discourse, it seemed to me that there was something amiss about how the Ruby community handles concurrency -- non-blocking servers (thin), with blocking database I/O (ActiveRecord), behind a round robin load balancer (nginx), combined with external processes for asynchronous tasks (sidekiq). That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
I am a proponent of Node, not because I think it is perfect, but I do think it does many things right. If you are building a significant web app you can't avoid JavaScript. And it is nice to be able to use and move code between the client and the server.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations, since there are magnitudes of performance differences between the two. I think the outcome will be better architected and performing applications, but that's just a hunch.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either. I think Go has a good chance, but time will tell.
Using synchronous I/O is only simpler if you never have to synchronize threads
No, but you have to synchronize callback chains, which is a lot worse in a large system. Otherwise it is kind of a tautology, well you don't have threads so you can't synchronize threads. But one main reason to synchronize threads is to prevent shared data from being corrupted from concurrent access. But now also the business logic is split based on IO points.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations,
No doubt it is important to be aware how these things work probably down to libc level. What makes node.js tick, v8 and libuv, what do those do, and so on.
since there are magnitudes of performance differences between the two.
Completely disagree. There are no general magnitudes of performance differences for all applications between those two. There are application specific and architecture specific.
I think the outcome will be better architected and performing applications, but that's just a hunch.
Spreading business logic across callback boundaries or sprinkling yields or thens, is not always a good way to handle things. It is in some cases but as a general approach, I think it is pretty bad.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either
One reason is because people don't understand the underlying technology and its limitations or are just not aware about other programming paradigms (especially when handling distributed and concurrent issues).
So I tend to not go along much with "well many are doing things this way so it must be better". Over the years I believe more that just following the "many" crowds get one an average result. Your stuff is just as broken or works just as well as anyone else.
That is why it is important to look at Go, Erlang, Rust, Haskell, Prolog etc. There provide new ways of thinking that could help you accelerate faster than the rest of the crowd.
That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
Node forces everything into a single-threaded event loop. That's great if you're writing a chat server. It's not so great if you want to do something that actually uses the CPU.
Ruby has threads (which execute in parallel on multiple CPU cores with JRuby/Rubinius), async I/O, and ways to build hybrid systems out of them cleanly, like Celluloid
But there is a lot of tooling required to solve problems seen by common web apps.
Node is doing it differently, and it has advantages and disadvantages. One disadvantage is if you are doing CPU bound work, you will have to consider how you design your app more carefully. But that's the case with any language or platform.
Event driven servers have been around since the dawn of the internet and the select() call. Node has simply made it easier to write them in a higher level language. I personally think the event model makes a lot of sense for writing network servers, which web servers are. Also having a high performance HTTP implementation built into a platform for building applications for the web is a significant benefit.
I think two prevalent models are going to emerge for writing concurrent servers in the future. Event driven platforms like Node and actor/messaging passing systems like Go and Erlang. The languages and platforms that do not do those things well, I believe, will become less popular.
Ruby developers who care about writing high performance, concurrent web applications aren't going to use Thin unless they're also going to be using an evented framework and libraries. So no Rails, no ActiveRecord. When Thin is used with Rails, it's usually running only one request per process. And I don't think that's very common: Unicorn and Passenger are much more common for request-per-process Rails deployments. Thin is available for evented setups, and Puma is available for threaded setups. Choosing mismatched or less than optimal stacks is hardly a Ruby-specific issue (or one Node avoids in any way other than removing the option entirely), and it tends to indicate either ignorance or a lack of need for the best performance possible.
nginx or Apache are used because they're specifically designed for serving public HTTP traffic, and they have a lot of other features that tend to come in handy. They're also extremely well-optimized for delivering static content. There's no compelling reason to reinvent the wheel there -- HTTP application containers mounted behind dedicated HTTP servers is a serviceable, easily understood model with a lot of distinct benefits.
Finally -- background processing is frequently CPU-intensive. Evented concurrency is generally not useful there. Ruby has great options for both threaded[1] and multiprocess background processes, and the ideal model might not even be the same as what you choose for the frontend.
Promises are great for replacing callbacks in the sense of "Here is a function I want you to call once and only once". You can't convert the callback parameter of http.Server.listen() to a promise.
I don't really get the confusion. First-class functions let you do all kinds of great things - I can do things like map across a list to generate a list of functions which I then pass to somewhere else that can left-fold them, which I have trouble visualizing how I would do in a pseudo-synchronous language.
I understand that people have strong preferences for how they like to write code and that's why it's great that there are tons of programming languages out there. Javascript is one of them.
According to Wikipedia, Go's concurrency-model is not safe, and that's a huge problem in my opinion:
"There are no restrictions on how goroutines access shared data, making race conditions possible. Specifically, unless a program explicitly synchronizes via channels or mutexes, writes from one goroutine might be partly, entirely, or not at all visible to another, often with no guarantees about ordering of writes.[36] Furthermore, Go's internal data structures like interface values, slice headers, and string headers are not immune to race conditions, so type and memory safety can be violated in multithreaded programs that modify shared instances of those types without synchronization."
You know you can write code with race conditions with nodejs too though, right? 2 callbacks called in the same closure can modify the same variable. I love node but after correcting many, many errors like this in intern code I question whether aync callbacks are strictly simpler than multithreading.
Use Erlang or Elixir then. They have truly isolated process heaps. Processes are lightweight and preemptively scheduled. You can spawn 100Ks of them on a single machine, each with its own isolated heap that can crash and not affect the rest of the system. That is kind of amazing.
For this, Node.js is supposed to run several processes. While one is tied up, another one works. This is almost as good as erlang actor model. You can have a small scheduler library figuring out who to send the next request to.
What I do think is that Node.js processes should be expected to crash at any time. Because a Node.js process serves many requests, it can run out of memory, or anything else. Node.js processes should be able to be restarted instantly, with the consequence of only a few dropped requests (in fact, the requests should be retried if they fail due to a crash, before reporting a failure to the client).
Comments
It's worse than that: it's true that in-process asynchronicity is a performance optimization, but it doesn't follow at all that the code needs to read asynchronously.
There is no performance reason to make developers hand-code in the continuation-passing style. That should be the job of a compiler or runtime environment.
Erlang does it. Stackless Python does it. Go does it.
The whole "look how fast Node is due to all my hand-rolled callbacks" is a total fallacy. It's a case of taking perverse pride in an accidental wart of your language.
Slowly the node community is maturing and beginning to realize that if they want to build robust, fault-tolerant software they need better primitives than hand-rolled callbacks. Hence the slowly rising mindshare of Promises, which are strictly better, but would be better still if the language itself baked them in implicitly (consider that nearly any value in a Haskell program is represented by a promise under the hood, but you never have to deal with them explicitly).
There is no performance reason to make developers hand-code in the continuation-passing style.
node certainly recognizes this. However, there is no need to do anything here since JS is taking care of this with ES6. Even if you don't want to use ES6, there are now multiple ways to compile it down to ES5.
Hence the slowly rising mindshare of Promises
Promises are declining, and honestly doesn't really solve the problem in most cases. A couple of years from now, you won't see promises on the server. You should see tjholowaychuk/visionmedia's co[1]; that's how JS is going to look like in future.
In terms of expressiveness, there isn't much to choose between dynamic languages. While it took some getting used to, programming JS wasn't that different from writing Python. However, JS/node is succeeding because this is the first time ever that we have a language/platform that is truly write-once, run-anywhere. A significant advantage in favor of node.
[1] https://github.com/visionmedia/co
Yes, I'm looking forward to ES6. And I agree that Node's strength is that Javascript is truly the lingua franca of the modern web, which I why I would even bother using it.
But the value of running everywhere is also why ES6 is not a panacea. As you say, soon we won't need promises "on the server". But I want my codebase to run correctly everywhere. Promises are a bandaid that I'll need for quite a while yet.
If you're not shipping your js code to the browser, then it's frankly kinda silly to write it on the server.
No one pay attention to this dude. Of course it isn't silly. Plenty of people build their servers with JS and put them into production.
Plenty of people build their servers with PHP and put them into production. It is still germane to ask if this is silly.
for LANG in Java Scala Ruby Python C C++ Groovy Clojure; do s/PHP/$LANG/; done
Something silly is like putting shit-tons of fish into a box and expecting it to make HTTP requests. Writing a web server in PHP, regardless of what you think of the language, isn't. Comments like that are just gonna clutter someone's Google search one day.
"Right tool for the right job".
If all you have is a room full of PHP developers and all you need is a REST site, then well you could do worse but I would like to discourage you from the practice.
Similarly, unless you have a highly specialized need - fun asynch chat server or complex app where business logic is being directly shared between client and server - I too would discourage locking one's self into that platform.
Javascript is not the worst thing ever, but why bother if you don't have to?
1) Laravel is a fantastic choice for building web servers if your team is primarily PHP. 2) There is no such thing as locking yourself into the JS platform, because JS has the advantage of being our lingua francua, is a general purpose programming language (servers, clients, and more!) AND is the language with the most momentum TODAY. Not last year, not 20 years ago.
Dont you mean you won't see standalone Promises, and instead Promises + Generators?
The linked page doesn't really convince me.
What do we get using this new shiny instead of the older shiny bluebird or the old shiny Q?
I'm impressed that you apparently completely failed to realise co is an abstractive layer over promises, something it states upfront:
What you yield from your generator is not magical pixie dusts, it's promises.
Actually, it's not promises. It's generators.
co thunkifies promises: https://github.com/visionmedia/co/blob/master/index.js#L209-...
The generator is the driver, but the driver on its own its useless if it's got nothing to drive.
And what it has to drive is promises (or thunks since most existing node code is thunks-based I guess it makes sense to support that. taskjs uses promises more or less exclusively for the same purpose, Python 3.4's asyncio uses behaving much like JS promises, etc...)
Seems you have misunderstood how it works. https://github.com/visionmedia/co/blob/master/index.js
Typically in apps using 'co', functions will return generators, not promises. Promises were probably added because of all the existing code out there.
Here is a file from my ongoing project, should explain how generators are being used. See getPosts() or addPost() https://github.com/jeswin/fora/blob/master/server/src/models...
[Edit: Just read your earlier (Gr.GP) comment. Might want to avoid the unnecessary snark, especially when there's a chance you might be wrong.]
No.
Again, generators in and of themselves don't do anything (for async code). Something needs to be generated/yielded, and that thing is the reification of an asynchronous operation. Such as a promise[0]. Generators and promises fulfil different roles, generators are used as imperative "sugar" for chaining promises.
The generators you yield will ultimately resolve into a series of async operations (promises or "thunks"), yielding a generator is convenience (not very useful convenience either, since `yield*` exists the ability to yield generators just complexifies the driver)
[0] and in the case of co a "thunk"[1], because node
You can also use plain functions, objects, array, and even other generators too. Least, that's what the framework tells me when I fuck up my tests. It's not just all promises under the hood.
First: reactor-based event loops aren't always a performance optimization. Round tripping I/O operations through an event loop adds latency. Node's approach is great if you have large numbers of mostly idle connections, such as in a chat or websocket server, but slower than a multithreaded, blocking approach when you have small numbers of active connections. What you really want is a system that supports both models, so you can choose the best one for your problem.
Some Node developers, for example the ones behind Meteor, have come to the conclusion that callback/promise-driven development is painful and tedious, and started wrapping up the asynchronous behavior in coroutines.
Unfortunately, to participate in this sort of world, where you wrap the asynchronous spaghetti into what the "Node.js Is Badass Rockstar Tech" video would call "sequential code, you know, the code you can read", each async library must be hand-plummed into a synchronous version that does the coroutine juggling.
This puts Node in exactly the situation it was trying to escape: now there are two different I/O models, one synchronous, one asynchronous, and not all libraries support the synchronous model, so by trying to leverage it, you're cutting yourself out of large parts of the Node ecosystem.
Better environments abstract over the I/O scheduling, letting you choose between threaded, blocking I/O, and a M:N task scheduler which can handle many lightweight tasks being scheduled on native threads. Rust comes to mind.
I wrote a post on this in a Go topic here not too long ago:
https://news.ycombinator.com/item?id=7388790
It was about how essentially, in a large concurrent system, a chain of callbacks like:
cb1 -> cb2 -> cb3|eb3 then cb3->cb4 and eb3->cb5
(select/epoll returns and calls cb1, chain ends with cb4 or cb5 depending if errback eb3 is called).
Is just a messy, dangerous and confusing re-implementation of threads/goroutines/tasks. Besides spreading the business logic among multiple io related function or sprinkling yields() or thens() it doesn't completely save one from needing locks and semaphores if shared or non-local data is modified.
A second callback chain from cb1 could have started before the previous one finished. Now they both could be modifying the same data. Yes granularity level in this is at code block level between IO points not assembly instruction, as the system grows large this problems becomes apparent.
I had to deal with it in a Python Twisted based framework. There is Twisted Semaphore and I had to use it.
The node.js and reactor-based event loops look _very_ nice in small demos and when the callback chain is shallow. HAproxy or nginx are good examples of this. They have shallow callback chain. Node.js demo example also look good, "Oh look you can serve 'Hello World' in 5 lines on a websocket!'" stuff like that.
As systems grow larger, callbacks chains as concurrency mechanisms start to suck.
It's all async under the hood because, in the end, you can only really resolve one thing at a time. Just because generators and promises let you write a more synchronous style of programming, essentially sugar, it doesn't mean that it's actually working in a true sync fashion.
When people start describing how Node works rather than bloviating blind praise, the first thought I have is, Windows. Yeah thats how windows works too. Im totally sold now!
Using synchronous I/O is only simpler if you never have to synchronize threads, which you always do.
After taking a closer look at Discourse, it seemed to me that there was something amiss about how the Ruby community handles concurrency -- non-blocking servers (thin), with blocking database I/O (ActiveRecord), behind a round robin load balancer (nginx), combined with external processes for asynchronous tasks (sidekiq). That is a lot of tooling to handle concurrency. Comparatively I think Node has some advantages.
I am a proponent of Node, not because I think it is perfect, but I do think it does many things right. If you are building a significant web app you can't avoid JavaScript. And it is nice to be able to use and move code between the client and the server.
Also, this might sound weird, but I think there are some benefits to forcing developers to think differently about blocking vs non-blocking operations, since there are magnitudes of performance differences between the two. I think the outcome will be better architected and performing applications, but that's just a hunch.
Maybe Erlang and Go do concurrency right with message passing and light weight threading models, but they haven't taken the web development world by storm either. I think Go has a good chance, but time will tell.
No, but you have to synchronize callback chains, which is a lot worse in a large system. Otherwise it is kind of a tautology, well you don't have threads so you can't synchronize threads. But one main reason to synchronize threads is to prevent shared data from being corrupted from concurrent access. But now also the business logic is split based on IO points.
No doubt it is important to be aware how these things work probably down to libc level. What makes node.js tick, v8 and libuv, what do those do, and so on.
Completely disagree. There are no general magnitudes of performance differences for all applications between those two. There are application specific and architecture specific.
Spreading business logic across callback boundaries or sprinkling yields or thens, is not always a good way to handle things. It is in some cases but as a general approach, I think it is pretty bad.
One reason is because people don't understand the underlying technology and its limitations or are just not aware about other programming paradigms (especially when handling distributed and concurrent issues).
So I tend to not go along much with "well many are doing things this way so it must be better". Over the years I believe more that just following the "many" crowds get one an average result. Your stuff is just as broken or works just as well as anyone else.
That is why it is important to look at Go, Erlang, Rust, Haskell, Prolog etc. There provide new ways of thinking that could help you accelerate faster than the rest of the crowd.
Node forces everything into a single-threaded event loop. That's great if you're writing a chat server. It's not so great if you want to do something that actually uses the CPU.
Ruby has threads (which execute in parallel on multiple CPU cores with JRuby/Rubinius), async I/O, and ways to build hybrid systems out of them cleanly, like Celluloid
Yes it is true, if your app is CPU bound, the event driven model has limitations. But many applications are not CPU bound.
There's a reason why that extra tooling exists: to solve a wider range of problems
But there is a lot of tooling required to solve problems seen by common web apps.
Node is doing it differently, and it has advantages and disadvantages. One disadvantage is if you are doing CPU bound work, you will have to consider how you design your app more carefully. But that's the case with any language or platform.
Event driven servers have been around since the dawn of the internet and the select() call. Node has simply made it easier to write them in a higher level language. I personally think the event model makes a lot of sense for writing network servers, which web servers are. Also having a high performance HTTP implementation built into a platform for building applications for the web is a significant benefit.
I think two prevalent models are going to emerge for writing concurrent servers in the future. Event driven platforms like Node and actor/messaging passing systems like Go and Erlang. The languages and platforms that do not do those things well, I believe, will become less popular.
Event driven I/O like this? (hey look ma, it's callback-free!)
https://github.com/celluloid/nio4r
https://github.com/celluloid/celluloid-io
Actor framekworks like this?
http://celluloid.io
High performance web servers like this?
https://gist.github.com/YorickPeterse/9555037
A few things:
Ruby developers who care about writing high performance, concurrent web applications aren't going to use Thin unless they're also going to be using an evented framework and libraries. So no Rails, no ActiveRecord. When Thin is used with Rails, it's usually running only one request per process. And I don't think that's very common: Unicorn and Passenger are much more common for request-per-process Rails deployments. Thin is available for evented setups, and Puma is available for threaded setups. Choosing mismatched or less than optimal stacks is hardly a Ruby-specific issue (or one Node avoids in any way other than removing the option entirely), and it tends to indicate either ignorance or a lack of need for the best performance possible.
nginx or Apache are used because they're specifically designed for serving public HTTP traffic, and they have a lot of other features that tend to come in handy. They're also extremely well-optimized for delivering static content. There's no compelling reason to reinvent the wheel there -- HTTP application containers mounted behind dedicated HTTP servers is a serviceable, easily understood model with a lot of distinct benefits.
Finally -- background processing is frequently CPU-intensive. Evented concurrency is generally not useful there. Ruby has great options for both threaded[1] and multiprocess background processes, and the ideal model might not even be the same as what you choose for the frontend.
[1]: I'll take the opportunity to mention my Ruby background processing system, Woodhouse: https://github.com/mboeh/woodhouse
Promises are great for replacing callbacks in the sense of "Here is a function I want you to call once and only once". You can't convert the callback parameter of http.Server.listen() to a promise.
I don't really get the confusion. First-class functions let you do all kinds of great things - I can do things like map across a list to generate a list of functions which I then pass to somewhere else that can left-fold them, which I have trouble visualizing how I would do in a pseudo-synchronous language.
I understand that people have strong preferences for how they like to write code and that's why it's great that there are tons of programming languages out there. Javascript is one of them.
According to Wikipedia, Go's concurrency-model is not safe, and that's a huge problem in my opinion:
"There are no restrictions on how goroutines access shared data, making race conditions possible. Specifically, unless a program explicitly synchronizes via channels or mutexes, writes from one goroutine might be partly, entirely, or not at all visible to another, often with no guarantees about ordering of writes.[36] Furthermore, Go's internal data structures like interface values, slice headers, and string headers are not immune to race conditions, so type and memory safety can be violated in multithreaded programs that modify shared instances of those types without synchronization."
You know you can write code with race conditions with nodejs too though, right? 2 callbacks called in the same closure can modify the same variable. I love node but after correcting many, many errors like this in intern code I question whether aync callbacks are strictly simpler than multithreading.
Use Erlang or Elixir then. They have truly isolated process heaps. Processes are lightweight and preemptively scheduled. You can spawn 100Ks of them on a single machine, each with its own isolated heap that can crash and not affect the rest of the system. That is kind of amazing.
Idiomatic go concurrency uses channels for synchronization and passing data around. You shouldn't be writing to shared data from multiple co-routines.
It's easy to say that, but when Go added a race detector, they immediately found 40 data races in the standard library
Which is impressive considering the size and number of contributors...
Those are just the data races they found, though. There are quite likely many more.
Sidebar: Rust solves this problem by tracking the lifetime of memory throughout the program, ensuring there's no unsafely shared mutable state.
For this, Node.js is supposed to run several processes. While one is tied up, another one works. This is almost as good as erlang actor model. You can have a small scheduler library figuring out who to send the next request to.
What I do think is that Node.js processes should be expected to crash at any time. Because a Node.js process serves many requests, it can run out of memory, or anything else. Node.js processes should be able to be restarted instantly, with the consequence of only a few dropped requests (in fact, the requests should be retried if they fail due to a crash, before reporting a failure to the client).