How to specify a "caused by" in a JavaScript Error? - javascript

In my NodeJS program, I parse some user JSON file.
So I use :
this.config = JSON.parse(fs.readFileSync(path));
The problem is that if the json file is not correctly formated, the error thrown is like:
undefined:55
},
^
SyntaxError: Unexpected token }
at Object.parse (native)
at new MyApp (/path/to/docker/lib/node_modules/myApp/lib/my-app.js:30:28)
...
As it is not really user friendly I would like to throw an Error specifying some user friendly message (like "your config file is not well formated") but I want to keep the stacktrace in order to point to the problematic line.
In the Java world I used throw new Exception("My user friendly message", catchedException) in order to have the original exception which caused that one.
How is it possible in the JS world?

What I finally did is:
try {
this.config = JSON.parse(fs.readFileSync(path));
} catch(err) {
var newErr = new Error('Problem while reading the JSON file');
newErr.stack += '\nCaused by: '+err.stack;
throw newErr;
}

There is an new Error Cause proposal for ECMAScript, and it reached stage-4 at TC34!
It means it will be in the next ECMAScript version!
https://github.com/tc39/proposal-error-cause
You would provide the cause as an error option:
throw new Error(`Couldn't parse file at path ${filePath}`, { cause: err });
The ES proposal only formalize it on the language level, but browsers/NodeJS should normally agree to log the full causal chain in practice (see https://github.com/nodejs/node/issues/38725)
As of today (end of 2021), Firefox Devtools are already able to log nested stacktraces!

Joyent released a Node.js package that can be used exactly for that. It is called VError. I paste an example of how you would use the pacakge:
var fs = require('fs');
var filename = '/nonexistent';
fs.stat(filename, function (err1) {
var err2 = new VError(err1, 'stat "%s"', filename);
console.error(err2.message);
});
would print the following:
stat "/nonexistent": ENOENT, stat '/nonexistent'

2021 Update: To chain exceptions in JS:
class MyAppError extends Error {
constructor(...params) {
super(...params)
if (Error.captureStackTrace) {
// This is the key line!
Error.captureStackTrace(this, this.constructor);
}
this.name = this.constructor.name
}
}
See the Mozilla docs on Error.captureStackTrace

Use a try / catch block:
try {
this.config = JSON.parse("}}junkJSON}");
//...etc
}
catch (e) {
//console.log(e.message);//the original error message
e.message = "Your config file is not well formatted.";//replace with new custom message
console.error(e);//raise the exception in the console
//or re-throw it without catching
throw e;
}
http://jsfiddle.net/0ogf1jxs/5/
UPDATE: If you really feel the need for a custom error you can define your own:
function BadConfig(message) {
this.message = message;
this.name = "BadConfig";
}
BadConfig.prototype = new Error();
BadConfig.prototype.constructor = BadConfig;
try {
this.config = JSON.parse("}}badJson}");
} catch(e) {
throw new BadConfig("Your JSON is wack!");
}
http://jsfiddle.net/kL394boo/
Lots of useful info at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

Related

Using Meteor.wrapAsync correctly

Hopefully this is a newbie question.
I have the following code that I am trying to convert to using meteor.wrapAsync. I am getting a "Exception while invoking method 'emailSend' ReferenceError: syncfunc is not defined" exception. What am i missing?
Stack Trace:
I20191031-06:21:16.246(-5)? Exception while invoking method 'emailSend' ReferenceError: syncfunc is not defined
I20191031-06:21:16.248(-5)? at MethodInvocation.emailSend (src/imports/api/email.js:13:27)
I20191031-06:21:16.249(-5)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1771:12)
I20191031-06:21:16.273(-5)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20191031-06:21:16.275(-5)? at Meteor.EnvironmentVariable.EVp.withValue (packages\meteor.js:1234:12)
I20191031-06:21:16.276(-5)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20191031-06:21:16.277(-5)? at Meteor.EnvironmentVariable.EVp.withValue (packages\meteor.js:1234:12)
I20191031-06:21:16.277(-5)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20191031-06:21:16.278(-5)? at new Promise (<anonymous>)
I20191031-06:21:16.279(-5)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20191031-06:21:16.280(-5)? at packages/ddp-server/livedata_server.js:559:43
email.js:
Meteor.methods(
{
emailSend(fromAddress, subject, emailText)
{
if (Meteor.isServer)
{
const { Email } = require('../server/email.js');
var syncFunc = Meteor.wrapAsync(Email.send);
var sendEmailReturn=syncfunc(fromAddress, subject, emailText);
return sendEmailReturn;
**//if I comment out the above three lines and uncomment the line below then the application works fine.**
//return Email.send(fromAddress, subject, emailText);
}
},
})
You don't need to use external callback to sync methods as Meteor supports "async" and "awaits" by default. Below is an example of using 'await' method.
Meteor.methods({
async emailSend(fromAddress, subject, emailText) {
const { Email } = require('../server/email.js');
var sendEmailReturn = await Email.send(fromAddress, subject, emailText);
}
});
I believe Meteor.defer is more suited to what you're trying to achieve here.
Example:
Meteor.methods({
'action_plus_email': function () {
// do something
Meteor.defer(() => {
Email.send(...)
})
return 'hello there, user';
}
})
https://www.meteor-tuts.com/chapters/1/meteorsnacks#Meteor-defer
https://apiko.com/blog/organization-of-email-sending-in-meteorjs/
And if you're are going to be sending many emails, please take a look at mail-time. It can be of great help.
https://github.com/VeliovGroup/Mail-Time

MongoDB - Mongoose - TypeError: save is not a function

I am attempting to perform an update to a MongoDB document (using mongoose) by first using .findById to get the document, then updating the fields in that document with new values. I am still a bit new to this so I used a tutorial to figure out how to get it working, then I have been updating my code for my needs. Here is the tutorial: MEAN App Tutorial with Angular 4. The original code had a schema defined, but my requirement is for a generic MongoDB interface that will simply take whatever payload is sent to it and send it along to MongoDB. The original tutorial had something like this:
exports.updateTodo = async function(todo){
var id = todo.id
try{
//Find the old Todo Object by the Id
var oldTodo = await ToDo.findById(id);
}catch(e){
throw Error("Error occured while Finding the Todo")
}
// If no old Todo Object exists return false
if(!oldTodo){
return false;
}
console.log(oldTodo)
//Edit the Todo Object
oldTodo.title = todo.title
oldTodo.description = todo.description
oldTodo.status = todo.status
console.log(oldTodo)
try{
var savedTodo = await oldTodo.save()
return savedTodo;
}catch(e){
throw Error("And Error occured while updating the Todo");
}
}
However, since I don't want a schema and want to allow anything through, I don't want to assign static values to specific field names like, title, description, status, etc. So, I came up with this:
exports.updateData = async function(update){
var id = update.id
// Check the existence of the query parameters, If they don't exist then assign a default value
var dbName = update.dbName ? update.dbName : 'test'
var collection = update.collection ? update.collection : 'testing';
const Test = mongoose.model(dbName, TestSchema, collection);
try{
//Find the existing Test object by the Id
var existingData = await Test.findById(id);
}catch(e){
throw Error("Error occurred while finding the Test document - " + e)
}
// If no existing Test object exists return false
if(!existingData){
return false;
}
console.log("Existing document is " + existingData)
//Edit the Test object
existingData = JSON.parse(JSON.stringify(update))
//This was another way to overwrite existing field values, but
//performs a "shallow copy" so it's not desireable
//existingData = Object.assign({}, existingData, update)
//existingData.title = update.title
//existingData.description = update.description
//existingData.status = update.status
console.log("New data is " + existingData)
try{
var savedOutput = await existingData.save()
return savedOutput;
}catch(e){
throw Error("An error occurred while updating the Test document - " + e);
}
}
My original problem with this was that I had a lot of issues getting the new values to overwrite the old ones. Now that that's been solved, I am getting the error of "TypeError: existingData.save is not a function". I am thinking the data type changed or something, and now it is not being accepted. When I uncomment the static values that were in the old tutorial code, it works. This is further supported by my console logging before and after I join the objects, because the first one prints the actual data and the second one prints [object Object]. However, I can't seem to figure out what it's expecting. Any help would be greatly appreciated.
EDIT: I figured it out. Apparently Mongoose has its own data type of "Model" which gets changed if you do anything crazy to the underlying data by using things like JSON.stringify. I used Object.prototype.constructor to figure out the actual object type like so:
console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);
And I got this:
THIS IS BEFORE: function model(doc, fields, skipId) {
model.hooks.execPreSync('createModel', doc);
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
Model.call(this, doc, fields, skipId);
}
THIS IS AFTER: function Object() { [native code] }
Which showed me what was actually going on. I added this to fix it:
existingData = new Test(JSON.parse(JSON.stringify(update)));
On a related note, I should probably just use the native MongoDB driver at this point, but it's working, so I'll just put it on my to do list for now.
You've now found a solution but I would suggest using the MongoDB driver which would make your code look something along the lines of this and would make the origional issue disappear:
// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://${REPLACE_mongodb_username}:${REPLACE_mongodb_password}#url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened
// Open MongoDB Connection
const open_database_connection = async () => {
try {
client = await MongoClient.connect(mongodb_uri);
} catch (err) { throw new Error(err); }
db = client.db(db_name);
};
exports.updateData = async update => {
// open database connection if it isn't already open
try {
if (!db) await open_database_connection();
} catch (err) { throw new Error(err); }
// update document
let savedOutput;
try {
savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
{ // filter
_id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
},
$set: { // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
update // update here - this is assuming that the update object only contains fields that should be updated
}
// If you want to add a new document if the id isn't found add the below line
// ,{ upsert: true }
);
} catch (err) { throw new Error(`An error occurred while updating the Test document - ${err}`); }
if (savedOutput.matchedCount !== 1) return false; // if you add in '{ upsert: true }' above, then remove this line as it will create a new document
return savedOutput;
}
The collection testing would need to be created before this code but this is only a one-time thing and is very easy - if you are using MongoDB Atlas then you can use MongoDB Compass / go in your online admin to create the collection without a single line of code...
As far as I can see you should need to duplicate the update object. The above reduces the database calls from 2 to one and allows you to reuse the database connection, potentially anywhere else in the application which would help to speed things up. Also don't store your MongoDB credentials directly in the code.

How to report console.error with Sentry?

I have application where some critical issues are reported with console.error but are not thrown so application might continue to run - possibly in crippled state.
It's necessary to report also console.error issues, but Sentry (Raven) library send to server only thrown exceptions.
Does someone knows how to solve this nicely ?
(ideally without need to rewrite all console.error calls, cause also some vendor libraries might still write output just into console)
As user #kumar303 mentioned in his comment to the question ... you can use the JS console integration Sentry.Integrations.CaptureConsole.
See https://docs.sentry.io/platforms/javascript/configuration/integrations/plugin/#captureconsole for documentation.
At the end you JS code to setup Sentry looks as follows:
import * as Sentry from '#sentry/browser';
import { CaptureConsole } from '#sentry/integrations';
Sentry.init({
dsn: 'https://your-sentry-server-dsn',
integrations: [
new CaptureConsole({
levels: ['error']
})
],
release: '1.0.0',
environment: 'prod',
maxBreadcrumbs: 50
})
If then someone calls console.error a new event will sent to sentry.
Here's a more robust override solution
// creating function declarations for better stacktraces (otherwise they'd be anonymous function expressions)
var oldConsoleError = console.error;
console.error = reportingConsoleError; // defined via function hoisting
function reportingConsoleError() {
var args = Array.prototype.slice.call(arguments);
Sentry.captureException(reduceConsoleArgs(args), { level: 'error' });
return oldConsoleError.apply(console, args);
};
var oldConsoleWarn = console.warn;
console.warn = reportingConsoleWarn; // defined via function hoisting
function reportingConsoleWarn() {
var args = Array.prototype.slice.call(arguments);
Sentry.captureMessage(reduceConsoleArgs(args), { level: 'warning' });
return oldConsoleWarn.apply(console, args);
}
function reduceConsoleArgs(args) {
let errorMsg = args[0];
// Make sure errorMsg is either an error or string.
// It's therefore best to pass in new Error('msg') instead of just 'msg' since
// that'll give you a stack trace leading up to the creation of that new Error
// whereas if you just pass in a plain string 'msg', the stack trace will include
// reportingConsoleError and reportingConsoleCall
if (!(errorMsg instanceof Error)) {
// stringify all args as a new Error (which creates a stack trace)
errorMsg = new Error(
args.reduce(function(accumulator, currentValue) {
return accumulator.toString() + ' ' + currentValue.toString();
}, '')
);
}
return errorMsg;
}
Based on #Marc Schmid's solution I came up with the following working example, if you link to the Sentry CDN files.
<script src="https://browser.sentry-cdn.com/5.11.1/bundle.min.js" integrity="sha384-r7/ZcDRYpWjCNXLUKk3iuyyyEcDJ+o+3M5CqXP5GUGODYbolXewNHAZLYSJ3ZHcV" crossorigin="anonymous"></script>
<!-- https://github.com/getsentry/sentry-javascript/issues/1976#issuecomment-492260648 -->
<script src="https://browser.sentry-cdn.com/5.11.1/captureconsole.min.js"></script>
<script>
Sentry.init({
dsn: 'https://abcdef1234567890#sentry.io/012345',
debug: false,
integrations: [
new Sentry.Integrations.CaptureConsole({
levels: ['error']
})
],
});
</script>
Found a little hacky solution:
const consoleError = console.error;
console.error = function(firstParam) {
const response = consoleError.apply(console, arguments);
Raven.captureException(firstParam, { level: 'error' });
return response;
};
It just wraps console.error and report each of error logs in console to Raven (Sentry).
If someone have nicer approach (maybe some hidden feature of Sentry) please feel free to share!
I wrote a library that is going this using your Sentry instance.
https://github.com/aneldev/dyna-sentry

Catch all JavaScript client-side errors on the server-side

How can I catch any exception that occurs in the client side code like "Pause On Caught Exceptions" on chrome developer tools?
I found the solution!
I have used the C# and MVC.
Add a new class to customize your js files bundle like this:
public class CustomScriptBundle : ScriptBundle
{
public CustomScriptBundle(string virtualPath) : base(virtualPath)
{
Builder = new CustomScriptBundleBuilder();
}
public CustomScriptBundle(string virtualPath, string cdnPath)
: base(virtualPath, cdnPath)
{
Builder = new CustomScriptBundleBuilder();
}
}
And, create another class to change the content of the js files as follows::
class CustomScriptBundleBuilder : IBundleBuilder
{
private string Read(BundleFile file)
{
//read file
FileInfo fileInfo = new FileInfo(HttpContext.Current.Server.MapPath(#file.IncludedVirtualPath));
using (var reader = fileInfo.OpenText())
{
return reader.ReadToEnd();
}
}
public string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<BundleFile> files)
{
var content = new StringBuilder();
foreach (var fileInfo in files)
{
var contents = new StringBuilder(Read(fileInfo));
//a regular expersion to get catch blocks
const string pattern = #"\bcatch\b(\s*)*\((?<errVariable>([^)])*)\)(\s*)*\{(?<blockContent>([^{}])*(\{([^}])*\})*([^}])*)\}";
var regex = new Regex(pattern);
var matches = regex.Matches(contents.ToString());
for (var i = matches.Count - 1; i >= 0; i--) //from end to start! (to avoid loss index)
{
var match = matches[i];
//catch( errVariable )
var errVariable = match.Groups["errVariable"].ToString();
//start index of catch block
var blockContentIndex = match.Groups["blockContent"].Index;
var hasContent = match.Groups["blockContent"].Length > 2;
contents.Insert(blockContentIndex,
string.Format("if(customErrorLogging)customErrorLogging({0}){1}", errVariable, hasContent ? ";" : ""));
}
var parser = new JSParser(contents.ToString());
var bundleValue = parser.Parse(parser.Settings).ToCode();
content.Append(bundleValue);
content.AppendLine(";");
}
return content.ToString();
}
}
Now, include your js files in application Bundles with your class:
BundleTable.Bundles.Add(new CustomScriptBundle("~/scripts/vendor").Include("~/scripts/any.js"));
Finally, in a new js file write customErrorLogging function as described below, and add it to your project's main html form:
"use strict";
var customErrorLogging = function (ex) {
//do something
};
window.onerror = function (message, file, line, col, error) {
customErrorLogging({
message: message,
file: file,
line: line,
col: col,
error: error
}, this);
return true;
};
Now, you can catch all exceptions in your application and manage them :)
You can use try/catch blocks:
try {
myUnsafeFunction(); // this may cause an error which we want to handle
}
catch (e) {
logMyErrors(e); // here the variable e holds information about the error; do any post-processing you wish with it
}
As the name indicates, you try to execute some code in the "try" block. If an error is thrown, you can perform specific tasks (such as, say, logging the error in a specific way) in the "catch" block.
Many more options are available: you can have multiple "catch" blocks depending on the type of error that was thrown, etc.
More information here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
see a small example how you can catch an Exception:
try {
alert("proper alert!");
aert("error this is not a function!");
}
catch(err) {
document.getElementById("demo").innerHTML = err.message;
}
<body>
<p id="demo"></p>
</body>
put you code in try Block and try to catch error in catch Block.

try catch error in javascript - get more error details

How can I get more error details from a javascript catch?
Are there more parameters to get more details from the caught error.
try {
var s = null;
var t = s.toString();
} catch(err) {
alert(err);
}
The Error Object has several properties that you can use. One property you can use to get the message of the error, is .message, as in:
catch(err) {
alert(err.message);
}
The .name property returns the type of error as in:
catch(err) {
x = err.name;
// ... do something based on value of x
}
The name describes the type of error, and the value of .name can be : EvalError, RangeError, ReferenceError, SyntaxError, TypeError , and URIError. You may decide to handle the error differently depending on the error type which is returned by the .name property.
A good tutorial can be found on JavaScriptKit. The is also an article on the error object at Mozilla Developer Network.
Check this link out:
Reference to Error.prototype
Basically you have err.name and err.message.
You also have a few vendor-specific extensions:
Microsoft => err.description and err.number.
Mozilla => err.fileName, err.lineNumber and err.stack.
function message()
{
try
{
}
catch(err)
{
alert(err.message);
}
}
SEE HERE and HERE

Categories

Resources