Operational errors vs. programmer errors
Don't throw to handle operational errors
Thrown errors should indicate that something is really broken. This is a circumstance where the NodeJs process should gracefully shutdown to come back in a clear state. If you handle operational errors in the same way you lose the ability to respond in a coordinated manner. Propagate errors always with the flow-control strategy of your choice Promises, Generators, Async / Await, Error-First-Callbacks. If throw an error in an asynchronous callback your error won't be catched because by the time the callback has been called, the surrounding try / catch block will have already exited. This is one reason more don't to propagate operational errors with try / catch.
Bad:
function divide(a, b) {
if (b === 0) {
throw new Error('Divide by Zero')
}
return a / b
}
try {
divide(1, 0)
} catch(error) {
console.error(error)
}
try {
setTimeout(() => divide(1, 0))
} catch(error) {
// error won't be catched because setTimeout is asynchronous
console.error(error)
}
Good:
function divide(a, b) {
if (b === 0) {
return Promise.reject(new Error('Divide by Zero'))
}
const result = a / b
return Promise.resolve(result)
}
divide(1, 0).catch((error) => {
console.error(error)
})
Better?:
function divide(a, b) {
if (b === 0) {
throw new Error('Divide by Zero')
}
return a / b
}
// Use divide synchronously
try {
divide(1, 0)
} catch(error) {
console.error(error)
}
// Use divide asynchronously
new Promise(resolve => setTimeout(resolve, 0))
.then(() => divide(1, 0))
.catch(error => console.error(error))
The distinction isn't business vs runtime errors. It's synchronous vs asynchronous errors. Exceptions are a synchronous error handling mechanism. So if we're calling divide asynchronously, then we'll need to convert its errors to an asynchronous rejection. Fortunately promises already do that for us behind the scenes.
Also, a big problem I see with your "good" code is that divide no longer returns a consistent type. It returns a number in the good case and a promise in the bad case. So before you can use the result or handle the error, you would need to type check the returned value.
Still good?:
function divide(a, b) {
if (b === 0) {
return Promise.reject(new Error('Divide by Zero'))
}
return a / b
}
const result = divide(1, x);
// Is result a number or a promise? Need to check
if (typeof result === 'number') {
// Use result
} else {
// Presume rejected promise
}
Hi @Jeff-Mott-OR my distinction is to explain that throwing errors shouldn't be used for everything. As long as you can handle the error you shouldn't throw an error you should reject it with promises or pass the error as first argument in the callback-style.
You example don't show the entrance situation. Also promises can't catch errors which was thrown asynchronously.
function divide(a, b) {
if (b === 0) {
throw new Error('Divide by Zero')
}
return a / b
}
// Uncaught Error: Divide by Zero
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(divide(1, 0))
})
})
.catch(error => console.error(error))
In your better example the divide function runs asynchronously but it's called synchronously in the promise.
Refer to your Still good example: Yes, you always have to check the return result for a critical function. My example was wrong you should return a fullfilled promise. I fixed it.
This suggestion should only show that if you throw errors for everything you have to fight with the synchronous nature of the try / catch block.
So it sounds like you want to Promise all the things? Every function should return a promise? Every error should be a rejection?
Also promises can't catch errors which was thrown asynchronously.
Ideally you'd use "then" to handle the fulfillment of an asynchronous action. That's what it's there for. And when you do that, like in my "Better?" example, then even ordinary synchronous functions fit in perfectly.
No, I don't want to promise all the things you should use the tool of you choice to handle asynchronous stuff but you shouldn't throw errors just to catch them with try / catch. What is when you can't split the asynchronous part from the synchronous? You will end up with:
Bad
const fs = require('fs')
// Uncaught Error: Test
new Promise((resolve, reject) => {
try {
fs.readFile('file', (error, file) => {
// business error
throw new Error('test')
resolve()
})
} catch (error) {
reject(error)
}
})
.then(result => console.log(result))
.catch(error => console.error(error))
Good:
const fs = require('fs')
// Uncaught Error: Test
new Promise((resolve, reject) => {
fs.readFile('file', (error, file) => {
// business error
reject(new Error('test'))
})
})
.then(result => console.log(result))
.catch(error => console.error(error))
As mentioned above: My tip is : Throw only errors if you don't know what can go wrong e.g parsing json etc.. for any other case use the async control flow style of your choice e.g callbacks or promises. It's bad style to throw all the errors. People from other languages e.g C# or Java are used to throw errors for everything. This can be a pitfall.
He puts it in a nutshell:
One should avoid throw errors as the way to pass error conditions around in applications. The throw statement should only be used "For this should never happen, crash and burn. Do not recover elegantly in any way"
That SE answer got it wrong. As do many, sadly, because of "exception"'s unfortunate name.
Here's a quote from Bjarne Stroustrup, the guy who invented C++:
Given that there is nothing particularly exceptional about a part of a program being unable to perform its given task, the word “exception” may be considered a bit misleading. Can an event that happens most times a program is run be considered exceptional? Can an event that is planned for and handled be considered an error? The answer to both questions is “yes.” “Exceptional” does not mean “almost never happens” or “disastrous.” Think of an exception as meaning “some part of the system couldn’t do what it was asked to do”.
I'm on your side but it doesn't answer that question. This article is worth to read https://www.joyent.com/node-js/production/design/errors
As we'll see, it's very uncommon to need to catch an error from a synchronous function. This is very different than Java, C++, and other languages that make heavy use of exceptions.
Straight from that article:
So, when do you use throw, and when do you use callbacks or event emitters? It depends on two things:
Is the error an operational error or a programmer error? Is the function itself synchronous or asynchronous?
By far, the most common case is an operational error in an asynchronous function. For the majority of these, you'll want to have your function take a callback as an argument, and you'll just pass the error to the callback.
The next most common case is an operational error in a synchronous function like JSON.parse. For these functions, if you encounter an operational error (like invalid user input), you have to deliver the error synchronously. You can either throw it (much more common) or return it.
We've left out programmer errors. ... You should throw these errors immediately, since the program is broken.
In other words:
- If it's a programmer error, just throw.
- If it's an operational error, then synchronous functions use synchronous error handling (exceptions), and asynchronous functions use asynchronous error handling (callbacks, promises).
The file system functions are almost all asynchronous, which is why they should use asynchronous errors. Whereas a function like "divide" is synchronous, which is why it should use synchronous errors.
I think we are moving from the suggestion I had. I am addressing a special case with asynchronous and synchronous error handling.
I think we can agree with:
Operational errors, which are anticipatable, unavoidable errors, even in correct programs (e.g., failing to connect to a server), and programmer errors, which are bugs in the program.
- In general, using
throwand expecting a caller to use try/catch is pretty rare expect for JSON.parse - Handle asynchronous errors with promises rejections or error-first-callback convention but never mix both. (This assumes that we shouldn't
throwin asynchronous functions which was my point in this issue.) - When delivering errors, use the standard Error class.
its difficult for me to understand the language with its xtras or special words. i cant see. dont understand correctly . some sentences are missing an i am not really knowing what to do or what i do...like that.
@lavinle an example:
In general, using throw and expecting a caller to use try/catch is pretty rare expect for JSON.parse
Bad:
function divide(a, b) {
if (b === 0) {
throw new Error('Divide by null')
}
return a / b
}
Good:
function divide(a, b) {
if (b === 0) {
return 0 // it depends on what the callee expect
}
return a / b
}
Bad 2:
function readFileSync(filename) {
const buffer = fs.readFileSync(filename)
if(!buffer.length) {
throw new Error('Empty buffer')
}
}
Good 2:
function readFileSync(filename) {
const buffer = fs.readFileSync(filename)
if(!buffer.length) {
return '' // it depends on what the callee expect
}
}
Handle asynchronous errors with promises rejections or error-first-callback convention but never mix both.
Bad:
function myApiFunc(callback) {
/*
* This pattern does NOT work!
*/
try {
doSomeAsynchronousOperation(function (err) {
if (err)
throw (err);
/* continue as normal */
});
} catch (ex) {
callback(ex);
}
}
Good:
function myApiFunc(callback) {
// synchronous operation
const result = add(1, 2)
// oh I expect something different
if(!result) {
return callback(new Error('Invalid operation'))
}
doSomeAsynchronousOperation(function (err) {
if (err)
return callback(ex);
/* continue as normal */
callback(null, result)
});
}