>
> Here's some working code:
"use strict";
var domain = require('domain');
var assert = require('assert');
// Implement a session class that keeps track of async operations
// Is uses a domain to handle errors in these operations
function Session() {
this.domain = domain.createDomain();
this.started = 0;
this.completed = 0;
this.isClosed = false;
this.parentDomain = process.domain;
this.errors = [];
this.results = [];
var theSession = this;
this.domain.on("error", function(err) {
theSession.errors.push(err);
operationComplete(theSession);
});
}
// Add a new (potentially async) operation to the session
function addOperation(operation) {
this.started++;
var session = this;
this.domain.run(function() {
operation(function(result){
operationComplete(session, result);
});
});
}
Session.prototype.addOperation = addOperation;
// close a session, that is, no longer allow new operation to be added
function closeSession(cb) {
this.isClosed = true;
this.callback = cb;
checkSessionComplete(this);
}
Session.prototype.closeSession = closeSession;
// Mark that an operation has completed
function operationComplete(session, result) {
if (result) {
session.results.push(result);
}
session.completed++;
checkSessionComplete(session);
}
// check whether all operations have completed
function checkSessionComplete(session) {
if (session.isClosed && session.completed == session.started) {
session.parentDomain.run(function() {
session.callback(session);
});
}
}
// Create and exercise a session
var mainDomain = domain.createDomain();
mainDomain.run(function() {
var session = new Session();
for (var i = 0; i < 10; i++) {
session.addOperation(function(cb) {
var shouldThrow = i % 2 == 1;
process.nextTick(function() {
if (shouldThrow) {
throw new Error();
}
else {
cb("done");
}
})
});
}
session.closeSession(allWorkDone)
});
function allWorkDone(session) {
assert.ok(session.isClosed);
assert.equal(session.errors.length, 5);
assert.equal(session.results.length, 5);
assert.equal(process.domain, mainDomain);
console.log("Done.");
}
This is simplified to save space, so, yes, I know it lacks error-checking
and handling of corner cases and that what it does it trivial :-)
The idea is that the Session manages a group of async operations. It runs
them all under a domain specific to the Session, and any errors caught are
simply stored in the Session. When all the operations are complete, the
Session calls back into the main logic, which should run under a different
domain. As you can see, they way it does this is to save the domain that
was active at the time the Session was created and use it for the call back
into the main logic (in checkSessionComplete())..
In real life, this is used to orchestrate web service calls. node.js
creates an HTTP server. Requests it receives are analyzed and a set of web
service calls are made under the control of a Session (which allows them to
be run in parallel inserted of series.) When all the web service calls are
complete, their responses are merged into the response to node's caller.
All of the work to process a node request is done under a request-specific
domain (to allow any unhandled errors to be formatted into an HTTP
response). The final callback from the Session must be done under that same
domain, which, since much async processing has occurred, is no longer in
the stack.