>
> 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.

Reply via email to