How do node.js domains work?

Node.js domains are really quite simple to understand. There is not alot of voodoo magic going on under the covers.

Let's start with a simple routine that will keep the node process running forever.

function showRunning(){  
	setTimeout(showRunning, 1000);  
	console.log("Still Running");  
}  
showRunning();  

This will output "Still Running" every second for eternity.

If we throw an exception within our function it will crash the node process.

function showRunning(){  
	setTimeout(showRunning, 1000);  
	console.log("Still Running");  
	throw new Error("Yikes");  
}  
showRunning();  

Now we could of course wrap the code that might cause an exception in a try catch block but that is no the point of this post. I want to demonstrate how domains work.

Another way to stop the node process from crashing is to trap the uncaughtException event of the process object.

process.on('uncaughtException', function(err) {  
  console.log('Caught exception: ' + err);  
});  
  
function showRunning(){  
	setTimeout(showRunning, 1000);  
	console.log("Still Running");  
	throw new Error("Yikes");
}  
  
showRunning();  

Now this will catch the exception and continue running after logging it to the console. This is a really bad idea by the way, it's shown in the documentation why it is such a bad idea, but I will use it here for demonstration purposes.

Now the simplest way of thinking about domains is that it is like placing a special uncaught exception handler in place and then removing it when you are finished. Something like:

function showRunning(){  
	function exceptionHandler(err){  
		console.log('Caught exception: ' + err);  
		process.removeListener('uncaughtException', exceptionHandler);  
	}  
	setTimeout(showRunning, 1000);  
  
	process.on('uncaughtException', exceptionHandler);  
  
	console.log("Still Running");  
	throw new Error("Yikes");
  
	process.removeListener('uncaughtException', exceptionHandler);  
}  
  
showRunning();  

Of course this is a crude simplification of how domains work and does not represent the full picture. The equivalent code using domains is:

var domain = require("domain");  
  
function showRunning(){  
	function exceptionHandler(err){  
		console.log('Caught exception: ' + err);  
	}  
  
	d = domain.create();  
	d.on('error', exceptionHandler)  
  
	setTimeout(showRunning, 1000);  
  
	d.enter();  
  
	console.log("Still Running");  
	throw new Error("Yikes");
  
	d.exit();  
}  
  
showRunning();  

You can also replace the calls to domain.enter() and domain.exit() with the domain.run() helper function as below.

	d.run(function(){  
		console.log("Still Running");  
		throw new Error("Yikes");  
	})  

Of course simplistic analogies always break down and this one has too. Domains are much more powerful than uncaughtException in that we can manage many of them within a node process. We can have a single domain per http request for instance to make sure that we can respond to the user when an exception occurs while processing their request. Domains manage the process of adding the right handler as we enter the domain and reverting back to the previous domain when we exit it.

The other major benefit of using domains is that it simplifies exception handling by giving us one method for both synchronous and asynchronous code (try catch only works for synchronous code). All you need to do is wrap your calls to async functions in domain.bind() or domain.intercept(). Domains automatically bind themselves to certain async functions and EventEmitters too.

I have shown previously that it is perfectly acceptable to use domains as a general error handling mechanism  and they don't add the same kind of performance overhead that try catch blocks can introduce. I have been using them extensively in a large project and found that they have reduced my boilerplate code and made it easier to ensure that exceptions have been correctly handled in my functions at a glance.

As an aside,  in v 0.8 of node, the uncaughtException event was what domains used themselves. As of v0.10 there is a similar but separate mechanism to allow domains and uncaughtException handlers to coexist.

Hope you find domains as useful as I have.