I'm used to thinking in Java and I'm trying to get my head around node.js. My program needs to log information when things go wrong, and I find I'm having to put in a lot of boilerplate code in my node.js program to get what I'd get for free in Java.
My question boils down to:
Here's a noddy Java program which tries (and fails) to connect to a Mongo database: import java.net.UnknownHostException;
import com.mongodb.Mongo;
public class Test {
public static void main(final String[] args) throws UnknownHostException {
final Mongo mongo = a();
}
private static Mongo a() throws UnknownHostException {
return b();
}
private static Mongo b() throws UnknownHostException {
return c();
}
private static Mongo c() throws UnknownHostException {
return new Mongo("non-existent host");
}
}
...which gives this helpful stack output:
Exception in thread "main" java.net.UnknownHostException: non-existent host
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$1.lookupAllHostAddr(Unknown Source)
at java.net.InetAddress.getAddressesFromNameService(Unknown Source)
at java.net.InetAddress.getAllByName0(Unknown Source)
at java.net.InetAddress.getAllByName(Unknown Source)
at java.net.InetAddress.getAllByName(Unknown Source)
at java.net.InetAddress.getByName(Unknown Source)
at com.mongodb.ServerAddress.updateInetAddress(ServerAddress.java:204)
at com.mongodb.ServerAddress.<init>(ServerAddress.java:73)
at com.mongodb.ServerAddress.<init>(ServerAddress.java:46)
at com.mongodb.Mongo.<init>(Mongo.java:138)
at Test.c(Test.java:20)
at Test.b(Test.java:16)
at Test.a(Test.java:12)
at Test.main(Test.java:8)
(In particular, the last 4 lines show me "what was happening" in my own code at the time the Mongo error occurred.)
Here's my attempt to re-write my program in node.js:
a(function (err, mongo) {
if (err) {
console.log("Something went wrong in main");
console.log(err);
}
});
function a(callback) {
b(function (err, mongo) {
if (err) {
console.log("Something went wrong in a()");
return callback(err);
}
return callback(null, mongo);
});
}
function b(callback) {
c(function (err, mongo) {
if (err) {
console.log("Something went wrong in b()");
return callback(err);
}
return callback(null, mongo);
});
}
function c(callback) {
var MongoClient = require('mongodb').MongoClient;
return MongoClient.connect('mongodb://non-existent host/', function (err, mongo) {
if (err) {
console.log("Something went wrong in c()");
return callback(err);
}
return callback(null, mongo);
});
}
...which gives this output:
Something went wrong in c()
Something went wrong in b()
Something went wrong in a()
Something went wrong in main
[Error: failed to connect to [non-existent host:27017]]
But to get this output, I have to put in lots of boilerplate code throughout my program, which is going to be a pain to police as my program gets larger and I have a whole development team.
Can I get this stack-like output another way? Is it un-node-like to expect this kind of output?
Promises are exactly what you are looking for (bring back the stack features to async code)
var Promise = require("bluebird");
var mongodb = require("mongodb");
// enable long stack traces, bluebird specific
Promise.longStackTraces();
// promisify mongodb so that it returns promises, also bluebird specific
Promise.promisifyAll(mongodb);
// raise stack limit, feature of v8/node.js
Error.stackTraceLimit = 100;
function c() {
var MongoClient = require("mongodb").MongoClient;
return MongoClient.connectAsync('mongodb://non-existent host/')
}
function b() {
return c()
}
function a() {
return b()
}
a().then(function(connection) {
});
Gives:
Possibly unhandled Error: failed to connect to [non-existent host:27017]
at null.<anonymous> (/home/petka/bluebird/node_modules/mongodb/lib/mongodb/connection/server.js:546:74)
at EventEmitter.emit (events.js:106:17)
at null.<anonymous> (/home/petka/bluebird/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:150:15)
at EventEmitter.emit (events.js:98:17)
at Socket.<anonymous> (/home/petka/bluebird/node_modules/mongodb/lib/mongodb/connection/connection.js:533:10)
at Socket.EventEmitter.emit (events.js:95:17)
at net.js:830:16
From previous event:
at Function.connectAsync (eval at makeNodePromisifiedEval (/home/petka/bluebird/js/main/promisify.js:199:12), <anonymous>:7:21)
at c (/home/petka/bluebird/throwaway.js:10:28)
at b (/home/petka/bluebird/throwaway.js:14:16)
at a (/home/petka/bluebird/throwaway.js:18:16)
at Object.<anonymous> (/home/petka/bluebird/throwaway.js:21:5)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3
You can use catch (named so because it works like a real catch statement) in one place:
a().catch(function(e) {
//handle e
});
Also bluebird specific features added to catch:
Predicated catches are also supported since it's just a method:
a().catch(SyntaxError, function(e) {
});
Predicate can be an error constructor or a predicate function
// define a predicate for IO errors
function IOError(e) {
return "code" in Object(e);
}