简体   繁体   中英

Variable passing in Node.js?

So, I'm trying to upgrade a chat system of mine to Node.js, and I haven't felt like such a noob in years!

In PHP, it's inefficient, but it definitely makes sense. Request starts up, it figures out the user information, room information, parses the message, and so on. Quite linear. It calls several functions in various places, but each time, it only ever has to send that function the information it needs directly. When that function is done, it returns, and then more work is done with something else using different information. If there's an exception, it (usually) gets caught at a level where the user can be alerted.

As I understand it, Node.js absolutely does not work like this, instead being powered largely by callbacks - and I have a great many callbacks going on. It has to handle the initial connection, and then it has to check if the cookie file exists, and then it has to read the cookie file, and then it has to get some user info from the database, and then it has to get some other user info from the database, and then it has to get some more user info from the database, and then it has to add the user to the room, which - if there hasn't been anyone there in a while - has to get the room info from the database, and then finally respond to the request. And there will be at least two more levels for permission checking when I'm done.

It's not much different from the PHP process, but in PHP it's multi-threaded through Apache, and so the request can sit there and wait for DB calls to return with no issue at all. User lookup, room subscriptions, permissions, all handled separately.

In Node.js, the "when you're done with that" system isn't too difficult to wrap my head around (I've used client-side JS and jQuery plenty), but variable passing certainly is. A big part of this is that try/catch is soundly defeated by callbacks. If the room data lookup query fails, that function needs to know what connection it should send the error back to (which could be two or three connections in the past by then), because it won't bubble up to a catch several levels back. So that connection object needs to be passed down through every single callback along the way. Which is only mildly disgusting when handling exceptions, since those could probably happen anywhere, but when you get to the point where other variables have to be passed all the way down the line for one callback at the end, my fingers refuse to type any further until I look into what has gone so terribly awry!

So I guess what I'm wondering, is if there's any "hack" I'm unfamiliar with that could allow variables to "jump" over non-nested callbacks. Having try/catch chain down indefinitely would be nifty, too.

EDIT: I'm having trouble trivializing hundreds of lines of code, so let's see if I can give some visual aid with the callback stack. Anything on the same line is a direct call, next line is a callback.

connection.on('messaage') -> controller.validateUser -> fs.exists
 fs.readFile
  function() -> controller.addUser -> factory.user -> user.refreshData -> db.query
   user.refreshChars -> db.query
    user.refreshBlocks -> db.query
     function() -> controller.addRoom -> factory.room -> room.refreshData -> db.query
      room.getRole -> db.query
       function() -> room.getUserList -> connection.sendUTF

As you can see, the functions are mostly located in objects rather than just nested unnamed functions, because they will often need to be accessed from multiple locations in arbitrary order. The problem is, some levels need the user object, and some don't. If try/catch were working properly, only the first and last would need to be aware of the connection to send information back.

What I need is a way to give these different functions different information, without having to flood every function before it with things they don't need. That is undesirable practice. I also need various user object functions to fail in very different ways - ways that object should not need to concern its self with, as it is the responsibility of the calling function.

initial(); // begins the process


  // this starts things off
function initial() {
    var props = {  // this is the common object
        onerror: function(err) {
                     if (err.msg === "reallyBadError")
                         return false; // false means stop
                     else
                         return true;  // true means we can continue
                 },
        someInitialData: {whatever:"data"}
    };

    doSomethingAsync(getFirstCallback(props));
}


function getFirstCallback(props) {

        // return the actual callback function
    return function(err, info) {
           // if callback was passed an error, handle it
        if (err && props.onerror(err) === false)
            return;

        props.info = info; // add something to props
        doAnotherAsync(getSecondCallack(props));
    };
}


function getSecondCallback(props) {

        // return the actual callback function
    return function(err, foo) {
           // if callback was passed an error, handle it
        if (err && props.onerror(err) === false)
            return;

        // maybe do something with props.info
        props.foo = foo; // add something to props
        doOneMoreAsync(getFinalCallack(props));
    };
}


function getFinalCallback(props) {

        // return the actual callback function
    return function(err, bar) {
           // if callback was passed an error, handle it
        if (err && props.onerror(err) === false)
            return;

        // maybe do something with props.info and props.foo

        // we also have access to the original props.whatever
    };
}

Here's a prototypal version:

var r = new Requester(); // begins the process


  // Here's the implementation
function Requester() {
    // "this" is the common object
    this.someInitialData = {whatever:"data"};

    doSomethingAsync(this.firstCallback.bind(this));
}

Requester.prototype.onerror: function(err) {
     if (err.msg === "reallyBadError")
         return false; // false means stop
     else
         return true;  // true means we can continue
 };

Requester.prototype.firstCallback = function(err, info) {
       // if callback was passed an error, handle it
    if (err && this.onerror(err) === false)
        return;

    this.info = info; 
    doAnotherAsync(this.secondCallack.bind(this));
};


Requester.prototype.secondCallback = function(err, foo) {
       // if callback was passed an error, handle it
    if (err && this.onerror(err) === false)
        return;

    this.foo = foo;
    doOneMoreAsync(this.finalCallack.bind(this));
};


Requester.prototype.finalCallback = function(err, bar) {
       // if callback was passed an error, handle it
    if (err && this.onerror(err) === false)
        return;

    // The final code    
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM