最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - "Call stack" for callbacks in node.js - Stack Overflow

programmeradmin8浏览0评论

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:

  • is there an easier/non-boilerplate way to get stack-like information in a chain of callbacks? and/or
  • am I guilty of failing to grasp node.js properly, and trying to force asynchronous node.js to be more like synchronous Java?

Java Example

Here's a noddy Java program which tries (and fails) to connect to a Mongo database: import java.UnknownHostException;

import .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.UnknownHostException: non-existent host
at java.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.InetAddress$1.lookupAllHostAddr(Unknown Source)
at java.InetAddress.getAddressesFromNameService(Unknown Source)
at java.InetAddress.getAllByName0(Unknown Source)
at java.InetAddress.getAllByName(Unknown Source)
at java.InetAddress.getAllByName(Unknown Source)
at java.InetAddress.getByName(Unknown Source)
at .mongodb.ServerAddress.updateInetAddress(ServerAddress.java:204)
at .mongodb.ServerAddress.<init>(ServerAddress.java:73)
at .mongodb.ServerAddress.<init>(ServerAddress.java:46)
at .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.)

Node.js Example

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?

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:

  • is there an easier/non-boilerplate way to get stack-like information in a chain of callbacks? and/or
  • am I guilty of failing to grasp node.js properly, and trying to force asynchronous node.js to be more like synchronous Java?

Java Example

Here's a noddy Java program which tries (and fails) to connect to a Mongo database: import java.UnknownHostException;

import .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.UnknownHostException: non-existent host
at java.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.InetAddress$1.lookupAllHostAddr(Unknown Source)
at java.InetAddress.getAddressesFromNameService(Unknown Source)
at java.InetAddress.getAllByName0(Unknown Source)
at java.InetAddress.getAllByName(Unknown Source)
at java.InetAddress.getAllByName(Unknown Source)
at java.InetAddress.getByName(Unknown Source)
at .mongodb.ServerAddress.updateInetAddress(ServerAddress.java:204)
at .mongodb.ServerAddress.<init>(ServerAddress.java:73)
at .mongodb.ServerAddress.<init>(ServerAddress.java:46)
at .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.)

Node.js Example

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?

Share Improve this question edited Jun 6, 2014 at 7:53 Bergi 666k161 gold badges1k silver badges1.5k bronze badges asked Jun 6, 2014 at 7:41 Renny BarrettRenny Barrett 1381 silver badge6 bronze badges 1
  • Promises might be what you're looking for; they convey the typical success and failure of (asynchronous) processes in a consistent manner. – Ja͢ck Commented Jun 6, 2014 at 7:58
Add a ment  | 

3 Answers 3

Reset to default 3

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._pile (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);  
 }

How about using stack trace from the error:

function trace(err)
{
    console.log(err);
    var stack = new Error().stack;
    console.log(stack);
}


a(function (err, mongo) {
    if (err)
    trace(err)
});

function a(callback) {
    b(function (err, mongo) {
        if (err)
        return callback(err);
        else
        return callback(null, mongo);
    });
}

function b(callback) {
    c(function (err, mongo) {
        if (err)
        return callback(err);
        else
        return callback(null, mongo);
    });
}

function c(callback) {
    var MongoClient = require('mongodb').MongoClient;
    return MongoClient.connect('mongodb://nohost/', function (err, mongo) {
        if (err)
        return callback(err);
        else
        return callback(null, mongo);
    });
}

Output

[Error: failed to connect to [non-existent host:27017]]
Error
    at trace (/myfolder/a.js:4:14)
    at /myfolder/a.js:11:2
    at /myfolder/a.js:17:10
    at /myfolder/a.js:26:10
    at /myfolder/a.js:36:10
    at /myfolder/node_modules/mongodb/lib/mongodb/mongo_client.js:406:11
    at process._tickCallback (node.js:339:11)

Now it is not as verbose as the one you got in Java, it does not give function names, but it helps in identifying the chain of execution.

longjohn seems to be very popular now a days for getting long stack traces in node.js

http://blog.nodejitsu./npmawesome-full-stack-traces-with-longjohn/

http://www.mattinsler./post/26396305882/announcing-longjohn-long-stack-traces-for-node-js

Here is a list of other such modules that might be helpful in identifying what you need

https://nodejsmodules/tags/stack

Example Code:

var longjohn = require('longjohn')
var stackTrace = require('stack-trace');
longjohn.empty_frame = 'ASYNC CALLBACKS :';
a();

function a() {

    b();
};

function b() {
    c();
}

function c() {

    setTimeout(function() {

        throw new Error("My Custom Error");

    }, 1000);

}

process.on('uncaughtException', function(err) {

    //in JSON format with the help of stack-trace module;
    console.error(stackTrace.parse(err));

    //in plain text format
    console.error(err.stack.trim());

    process.exit(1);

});

Output in plain text format with longjohn:

Error: My Custom Error
    at [object Object].<anonymous> (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:19:9)
    at listOnTimeout (timers.js:110:15)
ASYNC CALLBACKS :
    at c (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:17:2)
    at b (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:12:2)
    at a (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:8:2)
    at Object.<anonymous> (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:4:1)
    at Module._pile (module.js:456:26)
    at Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Module._load (module.js:312:12)

Output in plain text format without longjohn:

Error: My Custom Error
    at null._onTimeout (/Users/nav/Documents/workspace/stack_trace/hello-world-server.njs:19:9)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
发布评论

评论列表(0)

  1. 暂无评论