Zones – Robust error handling and flow control for Node.js
strongloop.com
strongloop.com
Promises/A+ is already standardized and is shipping in browsers. Yes, Promises/A+ is more complicated than it needs to be, and I wish a simpler, more monadic approach had won out. But this is an unnecessary flow-control fork, and it's not even a particularly good one by comparison. If Zones were built on top of the ES6 standard, all ES6-compatible promise libraries would immediately work with it (and there are many mature ones that already exist). No existing libraries work with Zones, and the existing ones will need to be amended to add Zone-specific interop.
God knows nobody needs a zone-to-promise converter; there are enough ways to do asynchronous flow control as it stands. This blog post seems to hope that Zones will some day make it into Node core; I sincerely hope they don't in their current state.
You should think of it more as an asynchronous try-catch block, where resources created in the "try" block are automaticially cleaned up.
I believe that flow control in the future will be done with generators; check out https://github.com/visionmedia/co for example.
> I believe that flow control in the future will be done with generators; check out https://github.com/visionmedia/co for example.
Generators and promises are both types of flow control that will be used in the future: they're both in the ES6 standard. In fact, visionmedia/co already supports promises.
> But Promises already provide asynchronous try/catch blocks
They do, if all you use is modules that return promises, and the node ecosystem currently has many modules that don't. For example:
zone.create(function MyZone() {
// Within MyZone
setTimeout(function() {
// Within MyZone
throw new Error("Oh noes!");
});
}).catch(function(err) {
// Back in the parent zone.
// Handle the error here.
});
The above won't work with promises unless you use a promisified version of setTimeout and return that promise from the constructor function.Conversely, with zones, chaining callbacks is hard to define conceptually:
zone.create(function MyZone() {
// In MyZone
}).then(function() {
// Which zone are we in?
}).then(function() {
// Which zone are we in?
});
But if you have a great idea here, bring it on.1. Creates the "Zone" and "Gate" abstractions, and, 2. Monkeypatches Node to use Zones and Gates.
To make setTimeout work, the zone module monkeypatches global.setTimeout and replaces it with a version that uses Zones and Gates. Rather than do that, what you could do is not use Zones and Gates at all, and monkeypatch global.setTimeout and replace it with a version that uses Promises. And: there, Promises work with setTimeout. Just as easily as Zones and Gates do.
(Alternatively, what many Promise libraries do is offer ways to generate "promisified" versions of individual non-Promise functions and encourage you to call those instead, rather than monkeypatching globals.)
My objection to zone is just the new abstractions, Zones and Gates. They don't seem to offer much (anything?) over Promises, and they seem needlessly incompatible with them. Instead of the two-part module, where you introduce Zones and Gates and also monkeypatch everything to use them, you could just have a one-part module that monkeypatches everything to use Promises. It would be less code and it would interoperate with the existing JS ecosystem.
I don't have a particular love for the Promise abstraction: it's just that everyone is going to have to learn and work with Promises anyway (since they're in ES6, and already in V8), and there's no reason to invent a new abstraction for asynchronous try/catch when one already exists. There are lots of Promise-supporting libraries, and no Zone-and-Gate-supporting ones, save for zone itself. Why bother with the fork?
What we're doing is make it unnecessary to add error handlers everywhere and still get caught by surprising failure modes. If the zone catches an error it cleans up (closes resources, cancel operations when that makes sense) and then routes the error to the zone's result callback so you can handle it in once place.
The 'status quo' is that node has domains, but zones should be conceptually cleaner and thus have less corner cases.
> one of them even has a null exception catch
I'm not sure what you mean here. The http demo catches an exception and prints the stack trace. We can do that because the zone cleans up the socket so it's not necessary to exit and restart node.
I can see why it's doing it, actually, because the connection is presumably done at that point and an error after everything important is done is less important, but it's poorly explained at best (as being to prevent the 'zone from exploding').
However it could be that some data has already been sent back, in which case writeHead would throw. If that happens there isn't much more we can do.
> What we're doing is make it unnecessary to add error handlers everywhere and still get caught by surprising failure modes. If the zone catches an error it cleans up (closes resources, cancel operations when that makes sense) and then routes the error to the zone's result callback so you can handle it in once place.
I'm really unclear on how the zone library is doing the magical interception of errors. In the curl example you don't put error handlers on things. But clearly something must be. Does a library (say, one that came from npm and not the core library) have to be zone-aware to function correctly with zones? What does using zones mixed with libraries that aren't zone-aware look like? How much complexity does it add to the library to make it zone aware?
It monkey patches the entry points into javascript from the binding layer (some wrapping happens at a higher level), for the purpose of keeping track of the active zone. At that level we also wrap a try-catch block around all callbacks.
> Does a library (say, one that came from npm and not the core library) have to be zone-aware to function correctly with zones
It depends. Most modules shouldn't need any modification. However there are certain not-so-common cases (for example, when you're implementing a connection pool) that the module would need to be made zone-aware. It's necessary when the module wants to call a callback (or any function) in a child zone from a parent/ancestor zone.
Right now that's not very easy - you'd have to use a Gate (https://github.com/strongloop/zone#gates). W're working on an easier API.
Edit: Conceptually it seems the same as promises. The main difference seems to be that they've added hooks [3] into the pipeline, and they duck punch the core API to intercept errors. Bluebird has solved this without duck punching using `Promise.promisifyAll(require("fs"))` [4]. I would love to see a write up on Zone.js's forks, because the article could of trivially been rewriten to use promises.
[1] http://strongloop.com/strongblog/comparing-node-js-promises-... [2] https://github.com/kriskowal/q#long-stack-traces [3] https://github.com/btford/zone.js/#zonefork [4] https://github.com/petkaantonov/bluebird
I also recommend watching his ng-conf talk [2] on Zone.js to get a better understanding of what zones enable.
[1] http://javascriptjabber.com/110-jsj-zones-with-brian-ford/
Is it a real problem? Seems that other popular projects like Ruby on Rails have a similar number of opened issues and it's not stopping them from releasing new versions.
Does the Node core team have any plans how to prevent this situation in the future?
I'm not sure the tag states are always up to date, perhaps they are, but the fact remains that 0.12 has been "real soon now" for quite a while, so saying it might be a while still is a pretty safe statement.
The core team is working hard, but I think the PR queue is a good indication of how scarce a resource review time is.