7 ms·
Error Handling in Node.js
- morghus 10y agoCan anyone explain why this pattern doesn't work? Or point me to some resource? function myApiFunc(callback) { /* * This pattern does NOT work! */ try { doSomeAsynchronousOperation(function (err) { if (err) throw (err); /* continue as normal */ }); } catch (ex) { callback(ex); } }
- mikevin 10y agoThere's a footnote about this: https://www.joyent.com/node-js/production/design/errors#fn:1 https://www.joyent.com/node-js/production/design/errors#fn:1
- morghus 10y agomuch obliged
- iotscale 10y agoTry/catch is not async and exceptions do not bubble up through the async context, which makes sense as the caller moved on with execution. Try this in your console: try { console.log("see, "); setTimeout(() => {throw new Error("oops")}, 100); console.log( "I can't assume here that" + " the prev. line succeeded" ); } catch(e) { console.log("error!"); }
- deleted 10y ago[deleted]
- visionscaper 10y agoThis article promotes the fail-fast approach, something I very much dislike (against popular opinion it seems). I'm very much in favor of the opposite approach, defensive coding. Often when I read opinion pieces about how bad defensive coding is, they almost always seem to forget that defensive coding without proper logging, error-handling and monitoring is NOT defensive coding. It is extremely dangerous to just detect error conditions without any feedback: you have no idea what is going on in your system! IMHO properly applied defensive coding, works as follows: * Detect inconsistent situations (e.g. in a method, expected an object as input argument, but got a null) * Log this as an error and provides feedback to the caller of the method that the operation failed (e.g. through an error callback). * The caller can then do anything to recover, (e.g. reset a state, or move to some sort of error state, close a file or connection, etc.). * The caller should then also provide feedback to its caller, etc. etc. This programming methodology gives the following advantages: * You are made to think about the different problems that can occur and how you should recover them (or not) * Highly semantic feedback about what is going wrong when an issue occurs; this makes it very easy to pinpoint issues and fix them * Server application keeps on running to handle other requests, or can be gracefully shut down. * Client side application UIs don’t break, user is kept in the loop about what is happening Of course you will need to keep a safety net to catch uncaught exceptions, properly logging and monitoring them (and restart your application if relevant) The fail-fast approach, as I have seen it applied, doesn’t do any checking or mitigation, with the effect that: - you are thrown out of you normal execution path, losing a lot of context to do any mitigation (close a file, close a connection, tell a caller something went wrong) - you only get a stack trace from which it can be hard to figure out what went wrong - there can be big impact on user experience : UIs can stop working, servers that stop responding (for all users). I have very good experiences with using the defensive coding paradigm, but it takes more work to do it right; for many, especially in the communities that use dynamic typing, such as the JS community, this seems to be a too big a hurdle to take. This is unfortunate because it IMO it could greatly improve software quality. Any feedback is welcome! (Edit: formatting to improve readability) (Edit: clarified defensive coding as an opposite approach to fail-fast)
- Silhouette 10y agoFWIW, I wouldn't suggest the term "defensive coding" as the opposite to "fail fast". It's very similar to the established term "defensive programming", which IMHO is more about designing systems to make fewer assumptions. How you then handle a situation where you do detect that some expectation has not been met, including the fail-fast strategy, seems like a related but separate issue. Terminology aside, though, I agree with much of what you say. The idea that it's generally acceptable for buggy code to just crash out seems to be making an unwelcome return recently, often among the same kinds of developers who don't like big design up front or formal software architecture because they want everything to be done incrementally and organically, and in the case of web apps specifically, often among developers who also consider code that runs for a year or two to be long-lived anyway.
- visionscaper 10y agoWith defensive coding I indeed meant defensive programming. You always want to fail fast (faster also means that you can fix more bugs), but this is often interpreted as "fail hard": no prevention or mitigation what so ever. In this sense I meant it is the opposite of defensive programming. What I notice is that developers who also have a background in statically typed (system) languages, are much more disciplined when it comes to defensive programming and logging/error handling. (I'm afraid this also correlates with age). BTW, I like your description, "designing systems to make fewer assumptions", for defensive programming!
- djanowski 10y agoI'm surprised such an in-depth article doesn't even mention promises. Upcoming async/await (already available via transpilation) will make error handling in Node sane again.
- jeffmcmahan 10y agoIf you're going to check every argument's type and throw on failure, either use a statically typed language or adopt a concise way of type checking. Many of the examples have big groups of assert() calls at the top. Gross.
- hueving 10y agoGiant post about the nightmare that is making robust code in node.js. Summary, don't use a language in large projects that makes it so easy to leak errors and exceptions. There's something to be said about the compiler forcing you to declare what exceptions your code can throw to force you to think about this stuff up front.
- Illniyar 10y agoNot really, just normal error handling issues like most other languages. You should always know how the api you are using returns an error and use it - most langauges have multiple common ways of notifying of an error. As to leaking, coming from Java's terrible error handling, you just start using runtime errors all the time anyway, at least with js the result is more concise.
- kbart 10y agoWhat better alternatives do you know? I don't know any programming language for real world projects that would make error handling trivial.
- wyager 10y agoTrivial? No. But you can at least make it better than the horrible nightmare described in the OP. Monadic error handling in a strongly typed language gives you a huge leg up on safely and sanely managing the complexity of error handling, because it provides simple value-level error semantics and clear type-level indications of exactly what kinds of errors you have to deal with and how you have to deal with them.
- ralusek 10y agoNode is actually best used with Promises, which aren't even mentioned in this post. Promises, aside from being far more concise with a huge amount of utility, do not leak errors or exceptions. doAMillionThings() .catch((err) => handleAnything(err));
- woodruffw 10y agoThe main conclusion I drew from this is that node.js has three "standard" ways to return/propagate an error, along with "traditional" methods (return code, global errno, etc). What's the deal? To someone who programs primarily in C and Ruby, this feels like a tremendous complication of the normal programming process.
- treve 10y agoPart of the problem in JS is that there's pretty much 2 classes of functions. Asynchronous functions and synchronous functions. Both are extremely common. Async/await solves this to some extent, because you can just go back to 1 way of error handling, which is throwing and catching exceptions. The third way (working with EventEmitter) is an odd pattern, but it's really more for specialized use-cases. Wouldn't really call this standard. Imagine a long-running operation that can occasionally broadcast that a non-fatal error occurred. A global error number is a terrible idea, and return codes are not just not idiomatic. So really there's just two: one for synchronous and one for asynchronous operations. You'd be in a very similar situation with C. I don't know C too well, but I imagine that most asynchronous operations would be done with threads, and for those operations you also can't just return an error code. Does Ruby have concurrency or async primitives? I don't know it really well. If it doesn't, it's also obvious why you wouldn't have this problem. If it does, how do you handle exceptions in asynchronous operations? To me it seems that Javascript, Ruby, C, PHP, Java are all pretty similar in these regards and JS is not at all unique. Go gets this right. The equivalent of this ES7 function call in javascript: await foo(); In go is a straight up regular function call: foo(); But not waiting for the result in javascript: foo(); Is actually handled with the go keyword: go func(); This, to me, is the major difference in the asynchronous model between Go and Javascript. In javascript (with ES7) blocking is opt-in, in Go it's opt-out. Go is by far the saner model for a programming language that relies heavily on 'green threads' / reactor pattern.
- prodigal_erik 10y agoThe trouble with "go foo()" is that it's fire-and-forget; foo's return value is literally discarded. When you need to know what happened (which should be nearly always), foo and every caller all have to opt-in to passing any result and/or error and/or panic value over a channel or something. It's one of many places where Go gives you tiny pieces of the right thing and makes you assemble them yourself.
- prodigal_erik 10y agoThis isn't stuff every programmer should know, it only concerns people who are trying to write complex non-blocking Javascript without async/await (which are already implemented in Babel and proposed for ES7). It also focuses on Node-only idioms which IMHO should be deprecated in favor of ES6 Promises (which Node's LTS release supports natively!)
- Illniyar 10y agoThe title of the post is just "error handling in node.js". Probably should change the submission title.
- logicallee 10y agoif you're not writing complex non-blocking Javascript in 2016, what are you even doing with your life? /s
- shark0 10y agoThis is a joke
- latch 10y agoMy non-node specific suggestions: 1 - Don't catch errors unless you can actually handle them (and chances are, you can't handle them). Let them bubble up to a global handler, where you can have centralized logging. There's a fairly old discussion with Anders Hejlsberg that talks about this in the context of Java's miserable checked exception that I recommend [1]. This is also why, in my mind, Go gets it wrong. 2 - In the context of error handling (and system quality), logging and monitoring are the most important thing you can do. Period. Only log actionable items, else you'll start to ignore your logs. Make sure your errors come accompanied by a date (this can be done in your central error handler, or at ingestion time (via logstash or what have you) 3 - Display generic/canned errors to users...errors can contain sensitive information. 4 - Turn errors you run into and fix into test cases. [1] - http://www.artima.com/intv/handcuffs.html http://www.artima.com/intv/handcuffs.html
- flukus 10y ago> This is also why, in my mind, Go gets it wrong. Considering the amount of places I see c# code catch errors and continue as though nothing happened I have to wonder how many wild go and c programmers simply ignore error codes?
- biztos 10y agoOnce I got used to it I found I kind of like the Go paradigm of checking for errors every time something could go wrong, and (usually) passing the first one up the chain with some additional context info. However, the fact that "ignore error" is an easy and built-in paradigm that even shows up in the official docs: fragileThing, _ := scary.MightNotWork() that fills me with dread.
- woah 10y agoI think that's in the docs mostly for conciseness. I don't think anyone thinks that it's a good practice in real code.
- 10y ago
- Silhouette 10y agoSome of this looks like horrible advice, particularly the defeatist attitude towards what the article calls "programmer errors". Statements to the effect that you can never anticipate or handle a logic error sensibly so the only thing you should ever do is crash immediately are hard to take seriously in 2016. What about auto-saving recovery data first? Logging diagnostic information? Restarting essential services in embedded systems with limited interactivity? This article basically dismisses decades of lessons learned in defensive programming with an argument about as sophisticated as "It's too hard, we should all just give up". As others have already mentioned, much of the rest is quite specific to Node/JS, and many of the issues raised there could alternatively be solved by simply choosing a better programming language and tools. The degree to which JS has overcomplicated some of these issues is mind-boggling.
- spc476 10y agoWhat about auto-saving recovering data? It really depends upon the language and environment used. I work with C (almost legacy code at this point), and if the program generates a segfault, there is no way to safely store any data (for all I know, it could have been trying to auto-save recovery data when it happened). About the best I can hope for is that it shows itself during testing but hey, things slip into production (last time that happened in an asynchronous, event driven C program, the programmer maintaining the code violated an unstated assumption by the initial developer (who was no longer with the company) and program go boom in production). At that point, the program is automatically restarted, and I get to pour through a core dump to figure out the problem. I'm not a fan of defensive programming as it can hide an obvious bug for a long time (I consider it a Good Thing that the program crashed otherwise we might have gone months, or even years, with noticing the actual bug). Logging is an art. Too little, and it's hard to diagnose. Too much and it's hard to slog through. There's also the possibility that you don't log the right information. I've had to go back and amend logging statements when something didn't parse right (okay, what are our customers sending us now? Oh nice! The logs don't show the data that didn't parse---the things you don't think about when coding). And then there are the monumental screw-ups that no one foresaw the consequences of. Again, at work, we receive messages on service S, which transforms and forwards the request to service T, which queries service E. T also sends continuous queries (a fixed query we aren't charged for [1]) to E to make sure it's up. Someone, somewhere, removed the fixed query from E. When the fixed query to E returned "not found," the code in T was written in such a way that failed to distinguish "not found" with "timedout" (because that fixed query should never have been deleted, right?) and thus, T shut down (because it had nothing to query), which in turn shut down S (because it had nothing to send the data to), which in turn meant many people were called ... Then there was the routing error which caused our network traffic to be three times higher than expected and misrouted UDP replies ... Error handling and reporting is hard. Maybe not cache invalidation and naming things hard, but hard none-the-less. [1] Enterprise system here.
- Illniyar 10y agoWhy the suggestion to use an error's name rather then instaneof and the error's class?
- prodigal_erik 10y agoError.prototype.toString() reads e.name, not e.prototype.constructor.name, so you can't rely on everyone to have subclassed Error. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/name https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- Illniyar 10y agoI'm not following, why can't I use: e instanceof Error or: e instanceof MyError why does toString() have anything to do with this?
- pags 10y agoThis is what I do when working with my own Error types.
- prodigal_erik 10y agoIt's very likely someone did const e = new Error('bad stuff happened') e.name = 'MyError' without actually creating a MyError class to check with instanceof.
- Illniyar 10y agoUnless it's common in popular libraries/packages, I don't see why I need to take it into account. Which popular libraries do this? If it's just in a few places, it should be handled specifically, and use sane choices in other places.
- wyager 10y agoI'm not really sure how to phrase this constructively, but this is horrible. Not the article, just the fact that humans expose themselves to this sort of stuff. Why would you choose to use a language that makes something as mundane as error handling this ridiculous and unpleasant?
- Illniyar 10y agoThere is nothing mundane about error handling, in fact it's one of the hardest things to get right in a programming language (see Rust's error handling saga for instance). There is no language I know of where error handling is both simple and not overbearing.
- alkonaut 10y agoThe most annoying thing about all this is that the central argument of this article "Separate recoverable errors from bugs" never made it to a widely used imperative language. C# had the opportunity but blew it. Java mixed the two kinds of exceptions up completely and checked exceptions just added insult to that injury. The best implementation I have seen for an imperative language is in Midori (The language used in Microsofts research OS with the same name). http://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors http://joeduffyblog.com/2016/02/07/the-error-model/#bugs-are... It's basically "C# done right". The blog post is well worth reading.
- Silhouette 10y agoThat the blog post is indeed very interesting reading.
- akssri 10y agoFor me, error handling has a major flaw: stack unwinding - extremely annoying thing to happen when the program state took many many hours to achieve. I don't think there is any language other than CL that allows restarts etc. to be defined; slime-repl too is invaluable when debugging. http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html http://www.gigamonkeys.com/book/beyond-exception-handling-co...
- chaitanya 10y agoRight! A long time back we came up with a really nice way of validating CSV files using restarts. I wrote a bit about it: http://lisper.in/restarts http://lisper.in/restarts
- akssri 10y agoNeat. I use it for things like linesearch and regularization (in optimization), https://github.com/matlisp/matlisp-optimization/blob/master/src/linesearch.lisp https://github.com/matlisp/matlisp-optimization/blob/master/... As a fellow Indian lisper, are you by any chance using CL for work ? Last I heard, the only big CL shop, cleartrip, moved all their codebase to Ocaml.
- chaitanya 10y agoNah, not using CL for work these days :-( I used to work at Cleartrip a long time back, before they moved off Lisp.
- akssri 10y agoAh, that's too bad :( CL is very very underrated as a language. Any insider info on why cleartrip moved away from Lisp ?
- chaitanya 10y agoSince it happened after I left, I am not privy to the exact reasons for the move. However, I guess they were worried about their Lispers moving away (which to some degree had already happened) and them not being able to find new ones.
- basicplus2 10y agoOn error goto...
- cutler 10y agoIt beats me why Node.js is anywhere near as popular as Elixir if real concurrency and error handling are a priority. Is programming just a fashion industry? What's popular certainly doesn't seem to have any connection with engineering principles.
- romanovcode 10y agoI blame non-technical managers who push "microservices" and "node js" because they went to some conference and heard that it's "the best".
- deleted 10y ago[deleted]
- novaleaf 10y agoForcing the party line of callback hell as a high quality "Production Practice" is an incredible disservice by not introducing the user to the concept of Promises. It already assumes a basic knowledge of exception handling, so at least they should hint at what is a saner choice.