2

Right now, it's a mystery to me if my try/catch blocks are going to work at all. I set them around code, and then, because something in the code was "asynchronous", which appears to be a fancy way of saying forked to another thread/process at the OS level, the try/catch is ignored if it happens in that code.

I'm fine with that, I just want to know if there's some indication of this? By convention, I'm given to understand, if a call asks for a callback, it's asych, otherwise it's not. I get why a callback means asych, but I'm afraid that the reverse isn't always true: THere's nothing stopping me from surrounding a call with a try/catch that gets loaded into a new call stack and also doens't ask for a callback. This seems really messy to me, and I'd like a little more control over my try/catches than using the default callback that all uncaught exceptions are handled by, if possible.

  1. Is there a semantic to tell me when code is going to leave the current stack?

UPDATE: here is an example:

var UserSchema = new mongoose.Schema({
  email: {type: String, unique: true},
  password: String,
  firstName: String,
  lastName: String,
  created_at: {type: Date, default: Date.now},
  updated_at: Date
});


var User = mongoose.model('User', UserSchema);

var users = [
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'Jones'},
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'Jones'}
];


users.forEach(function(user) {

          var newUser = new User(user);
          newUser.save(function(err, obj) {

            if (!err) console.log("Saved: ", obj.email);

          });

});

Given the code above, there's no way to catch an exception inside of save(), as it happens in another call stack. Is there any way for me to externally know that's what's about to happen when I call save()?

UPDATE: Everyone telling me to use handlers for this should maybe read this? It's clearly suggesting not to deal with exceptions that aren't caught in their "thread execution", quotes since it only acts like a thread.

Nathan C. Tresch
  • 938
  • 10
  • 24
  • You should write three questions instead of a big one. – Kijewski Apr 24 '12 at 19:28
  • 1
    I am going to assert that the lack of upvotes is disconcerting, as there have been a ton of responses and discussion, with people outright contradicting each other. Given that, I thought it was a pretty good question. :P – Nathan C. Tresch Apr 24 '12 at 20:44
  • Questions are underappreciated. The easiest questions get the most upvotes as the commenters want the question to have a high vote count to make their answer look more significant. Look at my questions. The easy, foolish ones got me many points, and the questions "that mattered" have merely one upvote. That's life. ;-) – Kijewski Apr 24 '12 at 21:07

4 Answers4

4

"Asynchronous" is not "a fancy way of saying forked to another thread/process".

JavaScript is single-threaded. End of story. There is no forking at the language level.

"Asynchronous" means just what it says: The order of execution is not the order of the code. Some bits of the code - callback functions - will execute at some point in time, when a certain event occurs. It is an event-based programming model.

Consider this simple example:

function hello () { alert("Hello!"); }

setTimeout(hello, 2000);

This is an asynchronous callback in its most basic form. You have a callback handler - the function hello - and an event generator, in this instance a time-based one.

Once the event occurs (2s have have passed), the callback handler is called.

Now for a modification:

function hello() { alert(foo.bar); }

try {    
  setTimeout(hello, 2000);
} catch (ex) {
  alert(ex.message);
}

We introduce a try-catch block around setTimeout. It guards the registration of a callback, nothing more. Most likely this step will succeed, hence the try-catch block will never do anything. The fact that the callback itself will fail in 2 seconds from now does not affect the try-catch block, naturally. This is the behavior you find confusing.

Now for another modification:

function hello() { 
  try {    
    alert(foo.bar); 
  } catch (ex) {
    alert("foo.bar is not defined");
  }
}

setTimeout(hello, 2000);

Now the try-catch block guards the step that can actually fail. This implies that you must use try-catch blocks where the errors can occur, not generally wrap them around large sections of your program (which is what you seem to do).

But how to get the exception to do do something useful and configuable? By introducing more callbacks, naturally.

function hello(onError) { 
  try {    
    alert(foo.bar); 
  } catch (ex) {
    onError("foo.bar is not defined", ex);
  }
}

function errorHandler(customMessage, exception) {
  alert(customMessage);
  // or do something with exception 
}

setTimeout(function () {
  hello(errorHandler)
}, 2000);

As per your added example:

var saveUsers = function(users, onSuccess, onError) {
  users.forEach(function(user) {
    var newUser = new User(user);

    newUser.save(function(err, obj) {
      if (err) {
        onError(err, obj);
        return false;
      } else {
        onSuccess(obj);
      }
    });
  });
}

var users = [
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'J'},
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'J'}
];

saveUsers(
  users, 
  function (user) { console.log("Saved: ", user.email); },
  function (err, user) { console.log("Could not save: ", user.email); }
});
Nathan C. Tresch
  • 938
  • 10
  • 24
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • And that explains why there is a seperate call stack with no discernable way to interact with it, because it's not an external thread under the hood? Sorry for the sarcasm, but, its a little tough to swallow that the things that act exactly like threads aren't, under the hood, and all of the ridiculousness of the try/catch not being able to catch their returns externally is just for fun. ;) All that said, you didn't answer my question, and none of your examples are what I meant. I'll edit my question and give one. – Nathan C. Tresch Apr 24 '12 at 19:53
  • Technically node isn't strictly single-threaded, since as far as I understand it does use threads under the hood for some tasks. Generally it's using `epoll` or something similar though to accomplish non-blocking I/O calls. – jches Apr 24 '12 at 19:57
  • 2
    @Nathan I believe the asynchronous concept confuses you. There is no need for sarcsm (and I don't find it very helpful). `try`/`catch` can do exactly what it's supposed to do. I believe it's your "synchronous thinking" that gets into your way. It does not matter in the least whether things you interact with, like HTTP requests, are implemented as threads outside of JavaScript. They generate events, and that is all that matters for your JS code. Just because things are not the way you expect them does not mean things are wrong. Your expectations could be wrong, as well. – Tomalak Apr 24 '12 at 19:59
  • @chesles Yes, that's partly true. I could imagine that some DB access libraries for node are threaded internally as well (I don't know). From the point of view of the JavaScript program, the world is strictly single-threaded, though. – Tomalak Apr 24 '12 at 20:01
  • @tomalak It's not that I'm confused, I don;t see a way to use exceptions in a way they are often used. I'd prefer to use normal try/catch, becuase, according to one of the Node.JS maintainers, http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb, what your describing should never be used. If an uncaught exception gets to your handler, you should restart the application. – Nathan C. Tresch Apr 24 '12 at 20:04
  • @Nathan See my addition regarding your example. Compare to my last code example that used `setTimeout`. It is the same thing. – Tomalak Apr 24 '12 at 20:16
  • @Nathan My bad, I should have used `obj` in the success/error callbacks. Fixed, try again. – Tomalak Apr 24 '12 at 20:29
  • Same thing. The error callback isn't being fired in the event of an uncaught exception. – Nathan C. Tresch Apr 24 '12 at 20:32
  • @Nathan Then the `if (err)` is wrong. What exactly is `err` in the case of a save error? – Tomalak Apr 24 '12 at 20:34
  • For the record: I'm not sure what part of my answer justifies an uncommented down-vote. – Tomalak Apr 24 '12 at 20:46
  • @Tomalak, for the record, I'm not clear how a question that spawned 4 answers in as many minutes and was discussed at length by several people, some of whom contradicted each other doesn't deserve an upvote. ;) – Nathan C. Tresch Apr 24 '12 at 20:48
  • @Tomalak maybe someone took exception to you insisting that Javascript doesnt use threading under the hood when that's the only way that non-concurrent execution/seperate call stacks would work out in a multithreaded OS? – Nathan C. Tresch Apr 24 '12 at 20:51
  • @Nathan You are right at that, done. That being said, for all I know my code should work, and do exactly what the `async` library is doing. There must be a simple mistake if it doesn't. -- I did not say that there was no threading at all. I said there is no threading in a JavaScript program, and this holds true. – Tomalak Apr 24 '12 at 20:52
  • THis is semantics, but, you saying "no", when I said "this seems to be a fancy way of saying it spawns a new thread at the OS level" indicates that you're saying it doesnt use threading under the hood. I clarified my sentance to indicate that more clearly, but, I thought it was clear in the first place that if I'm suggesting something is using threading in Node, I meant at the OS level. You might be right, I might have another bug in my code that the async library is hiding, I'll look. – Nathan C. Tresch Apr 24 '12 at 20:55
  • @Nathan And for all the JavaScript program is concerned with, it doesn't. You said "asynchronous = forking/threading" and that's simply not true. It can be an *interface* for a thread, but not necessarily, as `setTimeout` proves. The point is: *it doesn't matter*, and I said that, too. – Tomalak Apr 24 '12 at 20:59
  • "Mostly because, as a developer, really knowing what you're doing should be your goal." It should matter, as you yourself said. – Nathan C. Tresch Apr 24 '12 at 21:03
  • 1
    @Nathan Yes. If something has been *abstracted away*, there is a point where it does not matter anymore what's inside the black box. The event loop in JavaScript is such an example. It is your only interface with the world outside. It does not matter how the world outside looks if all you can receive from it is events. ;) But yes, it might be threads that create them. Or the TCP stack. Or processes. Or hardware interrupts. It doesn't matter. – Tomalak Apr 24 '12 at 21:09
  • @Tomalak Yeah. If it's abstracted away, I really should just deal with it and stop fighting the language I'm using. – Nathan C. Tresch Jan 16 '13 at 03:36
  • @Tomalak 20/20,as they say. – Nathan C. Tresch Jan 18 '13 at 01:09
2

The semantic is literally just when the function you called has finished executing. If it spawns an I/O process and registers an event, your try-catch block won't surround that because it's executed on another loop through the implicit Javascript event loop.

The presence or nonexistence of a callback parameter in the function you're executing has no bearing whatsoever on whether or not work started by the function will cause an event to fire off somewhere else. An EventEmitter-based object registers handlers with the .on('eventName', functionName) mechanism, so multiple events and multiple handlers can be accessing the same "work" but that is all kicked off by a function that takes no callbacks. While the Array object's forEach method takes a callback and is synchronous.

Simply put, nothing beyond the event loop barrier should cause a Javascript exception to be thrown. Only the code on the Javascript side of things can. So you put your try-catch blocks, if needed, on that side: In your function that calls the possibly async code if that function will possibly throw an error, and in your callback function itself if it calls something that could possibly throw an error. If it's async, they are two separate call stacks from Javascript's perspective, so they have different try-catch scopes; if it's sync, you'll just have one extra set of try-catch checks, and at least you'll have a better idea of what could have thrown the error.

Personally, I think try-catch just doesn't work in a language like Javascript, and was added to make it more Java-like, so I try to avoid code that uses throw for Node.js stuff. (Exceptions are if it's using them only for initial configuration of the library/object that can't work or if it's using it [poorly, in my opinion, because of the execution overhead] as an internal way to break out of deep synchronous code and doesn't expose it to me.)

EDIT: To better explain the callstack in Javascript, here's a simple ASCII diagram showing each level of the stack versus time:

== Javascript Event Loop =========================================================
== My Awesome Function ======================================    == My callback ==
    == Call to an async library ====================
        == I/O request and callback registration ===

Anything that is thrown by My callback will be sent straight back to the Javascript Event Loop and can only be caught by registering a process.on('uncaughtException', myFunc) handler to take care of it at that point. Basically, any code of your own can certainly use try-catch, but it should never throw if it'll be called directly as an event handler.

As for your recent edit to your question async.forEach will solve your issue. You pass it the array to iterate over, then the function to execute on each item in the array, and then a "finally"-style function to either handle an error or continue your code from there.

  • So essentially, there's no way to tell if a call is going to be run in its own call stack or not externally? – Nathan C. Tresch Apr 24 '12 at 19:55
  • David's advice to stay clear of `throw`, `try` and `catch` is basically sound. Use the node.js paradigm of passing errors as the first parameter to callbacks, instead. Keep passing the errors up the call chain until you can handle them, basically letting them bubble up similar to how exceptions bubble up in Java et al. – Linus Thiel Apr 24 '12 at 19:58
  • Please see my edit, there's no real way to do this @LinusGThiel, as I dont have control of what happens inside the save() method in my example. It will always end up being raised to the main portion of execution. – Nathan C. Tresch Apr 24 '12 at 20:08
  • @Linus Atomic operations that can fail - like, say, parsing a user-generated regex - should still have their try/catch block. Dis-recommending try/catch just because it gets harder to use in an async setting is not the best advise. try/catch simply must be kept at the function-local level, passing on errors must be done through callbacks. – Tomalak Apr 24 '12 at 20:08
  • Tomalak: Of course you are right. `JSON.parse` is a great example of something which is wise to surround with a try/catch. – Linus Thiel Apr 24 '12 at 20:10
  • Nathan: I see your edit. Calling asynchronous methods in a loop can be tricky. I'll post a response. – Linus Thiel Apr 24 '12 at 20:11
  • @Tomalak As near as I can tell, since there's no real way to handle exceptions thrown from outside the call stack, there's not much point in using them. They don't behave like any non-threaded environment I've ever worked with them in, and when the person responsible for uncaught exception being in the language says not to catch them, it starts to look like there's a good case not to use them for the most part. – Nathan C. Tresch Apr 24 '12 at 20:13
  • @Nathan Try-catch blocks are a synchronous concept. They do not work with asynchronous code. That doesn't mean you should not use them for the most part, it just means you should only use them in self-contained, monolithic, *synchronous* parts of your code. Like string parsing. Once you leave the synchronous flow of execution (every time a callback is involved), they stop to work, naturally. That doesn't make them useless, they are just the wrong tool for that job. – Tomalak Apr 24 '12 at 20:26
  • Since callbacks often dont fire in the event of an uncaught exception, there's basically no way to deal with them intelligently in node.js without loading a lib? That seems like the right answer to me at this point. http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb – Nathan C. Tresch Apr 24 '12 at 20:30
  • Basically, don't ever throw anything from your callback function because it's the "root" of the new call stack on the next run through the event loop. Let me edit my post with an ASCII diagram to try and explain that more clearly. –  Apr 24 '12 at 20:36
  • @Nathan The most interesting bit in that post is the sentence "unless you *really* know what you are doing". Mostly because, as a developer, really knowing what you're doing should be your goal. Loading off "magic" to an external library can't be the right way. – Tomalak Apr 24 '12 at 20:38
  • Using async, as @LinusGThiel suggests, is the only way to guarentee that the error is going to be passed correctly into a callback in my situation, as near as I can tell. The code provided elsewhere halts, and I'm given to understand it's because the async calls are happening in a loop, both in my case and the one that was provided by Tomalok. – Nathan C. Tresch Apr 24 '12 at 20:42
1

Nathan: I'm not going to go into the bigger question of finding out "when code is going to leave the current stack". I can help you with saving your users though. I recommend the great async library:

function saveUser(user, callback) {
    new User(user).save(callback);
}

async.forEach(users, saveUser, function(err, results) {
    // Respond
});
Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
  • 1
    This handles my problem without the need to know the answer to my question. :) – Nathan C. Tresch Apr 24 '12 at 20:40
  • Glad to hear that! The [async](https://github.com/caolan/async) library is a god send for any kind of loops/flow control in asynchronous code. The node.js event loop takes a while to wrap your head around, and in my experience you just have to accept the coding style. Basically, named functions helps code structure, and again, it's wise to stick to the node.js style of passing `err, result` to callbacks. `if(err) return next(err)` is pretty much the first line of any callback, until you're at a place where you can handle the error (usually in the request handler or so). – Linus Thiel Apr 24 '12 at 20:44
  • *Shameless self plug*: [This answer of mine](http://stackoverflow.com/questions/9362823/why-is-a-function-and-a-callback-non-blocking-in-node-js/9363071#9363071) can be of help to grok the general idea behind the event loop. – Linus Thiel Apr 24 '12 at 20:45
  • There are texts out there on the internet that say if something is thrown, the callback doesn't always fire, so, using callbacks to pretend to be finally blocks is cool but only if it works. I think error handling is an area Node obviously needs work. – Nathan C. Tresch Apr 24 '12 at 20:46
  • Nathan: Yeah, if mongoose would `throw` inside of `save` I would say that's a bug in mongoose. Definitely stick to battle tested libraries, and checking your dependencies' source code is always a wise thing to do, IMO. – Linus Thiel Apr 24 '12 at 20:48
  • A great many people use Mongoose, and with asyc this seems to be working. – Nathan C. Tresch Apr 24 '12 at 20:49
  • Yes, mongoose is a very popular library. I would be very surprised if it contained that type of bugs. – Linus Thiel Apr 24 '12 at 20:51
-1

If you're simply using callback functions, then it doesn't matter. NodeJS is single-threaded, thus everything is on the same call stack.

BUT! You're right about NodeJS may sometimes "leave" the current call stack. The leave is in quote because it's not really leaving the current call stack but simply returning to the root call (process.nextTick()). Then on the next tick, it's spawn a "new" call stack.

If you want a semantic, I guess you can say that anywhere that you're using EventEmitter, you're passing that callback function to the next tick and therefore "moving" it to another call stack. (But even so, it's not entirely true, because EventEmitter actually dispatches event on the current tick).

ming_codes
  • 2,870
  • 25
  • 24
  • This isn't exactly true, when using callback functions if does still matter, the callback never happens if an uncaught exception gets raised. – Nathan C. Tresch Apr 24 '12 at 20:10
  • 1
    This is confusing at best, and the assertion that "everything is on the same call stack" is simply wrong. – josh3736 Apr 24 '12 at 20:11