Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Call stack" for callbacks in node.js

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

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?

like image 269
Renny Barrett Avatar asked Jun 06 '14 07:06

Renny Barrett


People also ask

Does NodeJS have call stack?

The call stack is a LIFO (Last In, First Out) stack. The event loop continuously checks the call stack to see if there's any function that needs to run. While doing so, it adds any function call it finds in the call stack and executes each one in order.

How do I call a callback function in NodeJS?

For example: In Node. js, when a function start reading file, it returns the control to execution environment immediately so that the next instruction can be executed. Once file I/O gets completed, callback function will get called to avoid blocking or wait for File I/O.

What is event loop call stack?

The event loop is a constantly running process that monitors both the callback queue and the call stack. If the call stack is not empty, the event loop waits until it is empty and places the next function from the callback queue to the call stack.

What will happen if the call stack and the event loop are empty in node?

Features of Event Loop: The event loop executes tasks from the event queue only when the call stack is empty i.e. there is no ongoing task. The event loop allows us to use callbacks and promises. The event loop executes the tasks starting from the oldest first.


1 Answers

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);  
 }
like image 133
Esailija Avatar answered Sep 22 '22 18:09

Esailija