When to use domain.dispose()

In a previous post I showed that we can use domains as a replacement for try catch blocks in most scenarios. In fact I use them in just about every async function that I write.

Now I want to demonstrate an example where you might want to consider the use of domain.dispose(). Consider this contrived example.

var domain = require("domain");  
  
function withoutDispose(callback){  
	var d = domain.create().on("error", function(err) {  
		return callback(err);  
	});  
	d.enter();  
	setTimeout(function(){  
		throw new Error("Error Number 1");  
	},1000);  
	process.nextTick(function(){  
		throw new Error("Error Number 2")  
	})  
	d.exit();  
  
}  
  
withoutDispose(function(err){  
	if(err){  
		console.log("Received Error " + err)  
		return;  
	}  
	console.log("Did not Receive Error")  
});  

If you run this code you will find that the output is:

Received Error Error: Error Number 2  
Received Error Error: Error Number 1  

So, the error callback is being called twice. No problem in simple scenarios, but in more complex scenarios you might be rolling back changes to a database or something else significant in that callback.

What dispose does is go through any functions and EventEmitters that are bound to the domain and stop them from executing. The documentation states that "The intention of calling dispose is generally to prevent cascading errors when a critical part of the Domain context is found to be in an error state." This hints at a bigger problem that domain.dispose seeks to resolve.  That interdependencies between our code sections might set off a chain of errors calling the callback over and over again.

If we take the example above and place the call to d.dispose() before line 5  it will prevent the callback from firing twice.

Now, I first became aware of the need for domain.dispose when I started receiving multiple callbacks on my unit tests and couldn't figure out why. So that is a good indication that you might need to use it. The main reason for not using it everywhere is the following line in the documentation: "Streams are aborted, ended, closed, and/or destroyed. Timers are cleared. Explicitly bound callbacks are no longer called. Any error events that are raised as a result of this are ignored". So, it's a bit like transaction.abort from the RDBMS world.

Like transaction.abort, you don't use it everywhere because it might do more harm than good, but it is a useful tool to have around.