Best practice JavaScript rethrow preserving inner exception [duplicate] - javascript

This question already has answers here:
How to chain exceptions in javascript (ie add cause like in java)
(6 answers)
Closed 2 years ago.
function doFoo() {
throw new Error("no foo for you");
}
try {
doFoo();
} catch(e) {
throw new Error("doFoo() for shame!", e);
}
Is there any sane way to do this in JavaScript?
My specific objectives for the particular use case I'm asking this question are the following, in this order:
Follow standards, or at least best practices
Throw a new exception/error/whatever you want to call it (not just rethrow the thing I just caught); I'll call this an exception from now on
Preserve the message in the previous exception (I'd like to preserve the stack trace, but that's not as important for this particular use case)
Add extra metadata to the previous exception
Preserve the stack trace of the previous exception (nice to have, not really important to me right now)

How about a simple wrapper ?
class RootCause extends Error {
constructor(message, error) {
super(message);
if (error) {
this.cause = error;
}
}
}
function doFoo() {
throw new RootCause("no foo for you");
}
try {
doFoo();
} catch(e) {
throw new RootCause("doFoo() for shame!", e)
}
/** Prints:
RootCause [Error]: doFoo() for shame!
at Object.<anonymous> (/private/tmp/test.js:17:11)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
at Module.load (internal/modules/cjs/loader.js:986:32)
at Function.Module._load (internal/modules/cjs/loader.js:879:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47 {
cause: RootCause [Error]: no foo for you
at doFoo (/private/tmp/test.js:11:11)
at Object.<anonymous> (/private/tmp/test.js:15:5)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
at Module.load (internal/modules/cjs/loader.js:986:32)
at Function.Module._load (internal/modules/cjs/loader.js:879:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
**/

Related

Not able to understand the difference between the node versions which is resulting the difference between asserts

When i run the below statements using different node versions I see a difference in the assert outcome.
I am using v10.15.1 where the assert passes. But the same code in v14.18.1 throws error.
const assert = require('assert')
var err = new Error('some error');
var d = [{
'error':[err]
}]
var expected = [{
'error':[{}]
}]
assert.deepEqual(d,expected)
Error is as below:
assert.js:118
throw new AssertionError(obj);
^
AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:
[
{
error: [
Error: some error
at Object.<anonymous> (/Users/username/Desktop/repos/temp_files/test.js:2:11)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as r...
should loosely deep-equal
[
{
error: [
[]
]
}
]
at Object.<anonymous> (/Users/username/Desktop/repos/temp_files/test.js:9:8)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47 {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [
{
error: [
Error: some error
at Object.<anonymous> (/Users/username/Desktop/repos/temp_files/test.js:2:11)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47
]
}
],
expected: [ { error: [ [] ] } ],
operator: 'deepEqual'
}
I referred to the documentation for both the version but didn't find it useful.
v10
v14
I understand of course that the error object is empty in one but not in the other. But cannot find the reason why v10 ignores it and what changed later because of which the error is now caught
The behavior of assert.deepEqual was changed in Node.js 12 with this pull request. Part of the motivation behind the change was a requirement to align loose equal comparison with strict equal comparison whose behavior had been more predictable from the beginning. As a result, the assertion
assert.deepEqual(new Error('test'), { });
passes in Node.js < 12 but fails in Node.js 12 and later.
The simple explanation is that Errors are objects without own enumerable properties. In older version of Node.js, the fact that an Error and an empty object literal ({ })are both objects without own enumerable properties was reason enough for both to compare loosely equal. The behavior changed in Node.js 12 where the documentation of deepEqual states:
Error names and messages are always compared, even if these are not enumerable properties.
Granted, this does not make the change you observed obvious in any way.

google-spreadsheet.js (npm) is not working when follow the github example

When following the basic exmaple code from https://github.com/theoephraim/node-google-spreadsheet, it's not working.
for example:
const { GoogleSpreadsheet } = require('google-spreadsheet')
const creds = require('./gsheet-creds.json')
const doc = new GoogleSpreadsheet('1nkNX-HfxYY5qs_MwXTiFQIxEajibJlTTUPeOHCX7ZVc')
doc.useServiceAccountAuth({
client_email: creds.client_email,
private_key: creds.private_key,
})
await doc.loadInfo()
console.log(doc.title)
it shows error:
sz#air:/mnt/d/coding/wxBot-dorm-seller/googlesheet$ node gsheet-google-spreadsheet.js
/mnt/d/coding/wxBot-dorm-seller/googlesheet/gsheet-google-spreadsheet.js:34
await doc.loadInfo()
^^^^^
SyntaxError: await is only valid in async function
at wrapSafe (internal/modules/cjs/loader.js:1053:16)
at Module._compile (internal/modules/cjs/loader.js:1101:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)
at Module.load (internal/modules/cjs/loader.js:985:32)
at Function.Module._load (internal/modules/cjs/loader.js:878:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
If remove the 'await' before doc.loadInfo(), it shows error:
sz#air:/mnt/d/coding/wxBot-dorm-seller/googlesheet$ node gsheet-google-spreadsheet.js
gsheet#gsheet-yiouyou.iam.gserviceaccount.com
/mnt/d/coding/wxBot-dorm-seller/googlesheet/node_modules/_google-spreadsheet#3.0.13#google-spreadsheet/lib/GoogleSpreadsheet.js:175
if (!this._rawProperties) throw new Error('You must call `sheet.loadInfo()` before accessing this property');
^
Error: You must call `sheet.loadInfo()` before accessing this property
at GoogleSpreadsheet._ensureInfoLoaded (/mnt/d/coding/wxBot-dorm-seller/googlesheet/node_modules/_google-spreadsheet#3.0.13#google-spreadsheet/lib/GoogleSpreadsheet.js:175:37)
at GoogleSpreadsheet._getProp (/mnt/d/coding/wxBot-dorm-seller/googlesheet/node_modules/_google-spreadsheet#3.0.13#google-spreadsheet/lib/GoogleSpreadsheet.js:192:10)
at GoogleSpreadsheet.get title [as title] (/mnt/d/coding/wxBot-dorm-seller/googlesheet/node_modules/_google-spreadsheet#3.0.13#google-spreadsheet/lib/GoogleSpreadsheet.js:199:29)
at Object.<anonymous> (/mnt/d/coding/wxBot-dorm-seller/googlesheet/gsheet-google-spreadsheet.js:35:17)
at Module._compile (internal/modules/cjs/loader.js:1137:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)
at Module.load (internal/modules/cjs/loader.js:985:32)
at Function.Module._load (internal/modules/cjs/loader.js:878:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
I've tried many things and checked many examples from website and youtube. None of them is working for the latest google-spreadsheet. I haven't try anything older.
Appreciate any help here. Really want to get the google-spreadsheet.js working.
You are using async + await wrong. You should use "await" only when calling a declared "async" function. See this answer for a detailed explanation of the use.
Put the last 2 lines in an asynchronous function.
I think that will help you.
For example:
const loadDoc = async (creds) => {
await doc.loadInfo();
let sheet = doc.sheetsByIndex[0];
console.log(sheet.title);
};
loadDoc(creds);

How to run jQuery in node.js [duplicate]

This question already has answers here:
Can I use jQuery with Node.js?
(21 answers)
Closed 4 years ago.
I have tried npm install jQuery --save and in my node file var $ = require('jquery'); but, then when I run my node file with
$.getJSON('https://en.wikipedia.org/wiki/Washington,_D.C.', function(data) {
//data is the JSON string
});
I get the error
TypeError: $.getJSON is not a function
at Object.<anonymous> (C:\Users\Karim\node 2\tweetPic.js:16:3)
at Module._compile (module.js:643:30)
at Object.Module._extensions..js (module.js:654:10)
at Module.load (module.js:556:32)
at tryModuleLoad (module.js:499:12)
at Function.Module._load (module.js:491:3)
at Function.Module.runMain (module.js:684:10)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:608:3
I have also tried importing jquery using
require("jsdom").env("", function(err, window) {
if (err) {
console.error(err);
return;
}
var $ = require("jquery")(window);
});
which just returns
TypeError: require(...).env is not a function
at Object.<anonymous> (C:\Users\Karim\node 2\tweetPic.js:3:18)
at Module._compile (module.js:643:30)
at Object.Module._extensions..js (module.js:654:10)
at Module.load (module.js:556:32)
at tryModuleLoad (module.js:499:12)
at Function.Module._load (module.js:491:3)
at Function.Module.runMain (module.js:684:10)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:608:3
I have installed the jsdom package in a similar fashion. Is there something wrong with my jquery code itself? How can I fix this?
Edit: It seems jQuery isn't really what I need here. I'm just going to look into a different way of retrieving json data.
Sorry but i don't know how to install jquery. But you apparently need it to request a website and fetch json. You could use request for that. Hope i helped you.
And you could do like :
request('https://en.wikipedia.org/wiki/Washington,_D.C.', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});

Merging stack traces in rethrown errors

I'm rethrowing here an error from Sequelize promise (Bluebird). In the first place, this was done to change error message, but as it appeared, this also produces more informative stack trace.
It is something like
sequelize.sync().catch(originalError => {
const rethrownError = new Error(originalError.msg + ': ' + originalError.sql);
throw rethrownError;
});
Where originalError.stack doesn't contain the line that caused the error but it holds important information that it originates in Sequelize and MySQL driver:
SequelizeDatabaseError: ER_KEY_COLUMN_DOES_NOT_EXITS: Key column 'NonExisting' doesn't exist in table
at Query.formatError (...\node_modules\sequelize\lib\dialects\mysql\query.js:175:14)
at Query._callback (...\node_modules\sequelize\lib\dialects\mysql\query.js:49:21)
at Query.Sequence.end (...\node_modules\mysql\lib\protocol\sequences\Sequence.js:85:24)
at Query.ErrorPacket (...\node_modules\mysql\lib\protocol\sequences\Query.js:94:8)
at Protocol._parsePacket (...\node_modules\mysql\lib\protocol\Protocol.js:280:23)
at Parser.write (...\node_modules\mysql\lib\protocol\Parser.js:74:12)
at Protocol.write (...\node_modules\mysql\lib\protocol\Protocol.js:39:16)
at Socket.<anonymous> (...\node_modules\mysql\lib\Connection.js:109:28)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
at TCP.onread (net.js:548:20)
rethrownError.stack contains the point of interest (the first line in the stack) but everything else is a rubbish:
Error: ER_KEY_COLUMN_DOES_NOT_EXITS: Key column 'NonExisting' doesn't exist in table
at sequelize.sync.catch (...\app.js:59:17)
at tryCatcher (...\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (...\node_modules\bluebird\js\release\promise.js:504:31)
at Promise._settlePromise (...\node_modules\bluebird\js\release\promise.js:561:18)
at Promise._settlePromise0 (...\node_modules\bluebird\js\release\promise.js:606:10)
at Promise._settlePromises (...\node_modules\bluebird\js\release\promise.js:681:18)
at Async._drainQueue (...\node_modules\bluebird\js\release\async.js:138:16)
at Async._drainQueues (...\node_modules\bluebird\js\release\async.js:148:10)
at Immediate.Async.drainQueues (...\node_modules\bluebird\js\release\async.js:17:14)
at runCallback (timers.js:637:20)
at tryOnImmediate (timers.js:610:5)
at processImmediate [as _immediateCallback] (timers.js:582:5)
I would like to keep the information about both of them - and to designate the link between them, not just to add as two unrelated log entries.
I've been thinking on logging them as a single error with concatenated stack, rethrownError.stack += '\n' + originalError.stack.
How should these two errors be treated? Should their stack traces be joined? Is there a convention for merging error stacks in JavaScript (Node.js in particular)?
The intention is to keep the resulting error meaningful and to not upset existing tools that parse error stack traces (namely Stacktrace.js).
The projects under consideration use Winston logger or plain console.error, so the error is stringified at some point (in the example above it was logged via unhandled rejection handler).
🚨 UPDATE 2022: Error.prototype.cause has been implemented in ES2022. Consider using the native implementation.
Since ECMAScript 2022, new Error() lets us specify what caused it:
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(
`While processing ${filePath}`,
{cause: error}
);
}
});
}
See also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
Original answer:
Here is a lightweight alternative to VError: rerror
(I’m the author)
The idea is the same: Wrapping errors in errors. However it is much simpler. It has less features, but also works in the browser. It also takes into account that creating stack traces is expensive. Instead of creating stack traces and appending them to a string it creates a stack of errors internally and only creates the big stack trace if you need it (use the getter).
Example
function fail() {
throw new RError({
name: 'BAR',
message: 'I messed up.'
})
}
function failFurther() {
try {
fail()
} catch (err) {
throw new RError({
name: 'FOO',
message: 'Something went wrong.',
cause: err
})
}
}
try {
failFurther()
} catch (err) {
console.error(err.why)
console.error(err.stacks)
}
Output
FOO: Something went wrong. <- BAR: I messed up.
Error
at failFurther (/Users/boris/Workspace/playground/es5/index.js:98:11)
at Object.<anonymous> (/Users/boris/Workspace/playground/es5/index.js:107:3)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Module.runMain (module.js:590:10)
at run (bootstrap_node.js:394:7)
<- Error
at fail (/Users/boris/Workspace/playground/es5/index.js:88:9)
at failFurther (/Users/boris/Workspace/playground/es5/index.js:96:5)
at Object.<anonymous> (/Users/boris/Workspace/playground/es5/index.js:107:3)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Module.runMain (module.js:590:10)
A recommended read: https://www.joyent.com/node-js/production/design/errors
As far as I know, there is no built-in way to handle nested errors in Node.js. The only thing I can recommend you is to use the VError library. It is really useful when dealing with advanced error handling.
You can use fullStack to combine stack traces of many errors:
var err1 = new VError('something bad happened');
var err2 = new VError(err1, 'something really bad happened here');
console.log(VError.fullStack(err2));
Building upon https://stackoverflow.com/a/42147148/1703845, I abstracted the VError.fullStack call away like this
class FullStackVError extends VError {
constructor(cause, ...args) {
super(cause, ...args);
let childFullStack;
if (cause instanceof VError) {
childFullStack = cause.stack;
cause.stack = cause._originalStack;
}
this._originalStack = this.stack;
this.stack = VError.fullStack(this);
if (cause instanceof VError) {
cause.stack = childFullStack;
}
}
}
Now console.log(err2.stack); is equivalent to what console.log(VError.fullStack(err2)); would have been.

Extending Error gives wrong stack trace information

This question is not the same as Extended Errors do not have message or stack trace, which deals with es6 extends, but I'm using es5.
Here's my code
test.js
function FooError(message) {
this.stack = (new Error(message)).stack;
console.log('this.stack', this.stack);
console.log('new_error_stack', (new Error(message)).stack);
console.log('after');
}
FooError.prototype = new Error;
throw new FooError("foo");
This gives the following output : with node version 6.4 (node test.js)
this.stack Error
at Object.<anonymous> (/tmp/test.js:7:22)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Module.runMain (module.js:590:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
at bootstrap_node.js:509:3
new_error_stack Error: foo
at Error.FooError (/tmp/test.js:4:33)
at Object.<anonymous> (/tmp/test.js:9:7)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Module.runMain (module.js:590:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
after
/tmp/test.js:9
throw new FooError("foo");
^
Error
at Object.<anonymous> (/tmp/test.js:7:22)
at Module._compile (module.js:556:32)
at Object.Module._extensions..js (module.js:565:10)
at Module.load (module.js:473:32)
at tryModuleLoad (module.js:432:12)
at Function.Module._load (module.js:424:3)
at Module.runMain (module.js:590:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
at bootstrap_node.js:509:3
as you can see, the 'this.stack' doesn't show the same thing at all than the 'new_error_stack', eg, the this.stack shows the stack as if the error was thrown when the require was called. Also, the thrown stack is also "incorrect".
I took the code from https://stackoverflow.com/a/5251506/1993501 , and since I want to use instanceof FooError, I use the line FooError.prototype = new Error.
As you can see, the 'new_error_stack' gives the expected output, but I would have expected both values to be equal.
This has the effect that errors are not very useful for users of my library, see corresponding issue : https://github.com/open-xml-templating/docxtemplater/issues/245
What exactly is happening, and how can I prevent it and keep proper error types (eg being able to use instanceof) ?
I found a possible solution to your problem, but I'm not entirely sure it fixes all of your issues. I'm using Error.prototype instead of new Error (which is what you had). I found this solution from the first answer provided to this question: How do I create a custom Error in JavaScript?
I also changed your console.log lines to concatenate the two strings, as I was using alert() to display the output for testing purposes.
Kevin Hakanson goes over why you may or may not want to go with this solution in his answer. Here's the code to answer your question:
function FooError(message) {
this.stack = (new Error(message)).stack;
console.log('this.stack ' + this.stack);
console.log('new_error_stack ' + (new Error(message)).stack);
console.log('after');
}
FooError.prototype = Error.prototype;
throw new FooError("foo");
You may want to use a try/catch block with that last line.

Categories

Resources