Throw multiple exceptions in JavaScript - javascript

I'm wondering if there is a way to throw multiple errors and catch all of them in JavaScript.
I'm looking for specific fields, and when I find a missing one I would like to throw an error.
The problem is that if I throw a single error, like:
throw "you are missing field: " xxxxxx
I'm not telling the user all of the other fields which he/she is missing.
I would also not like to combine everything into a single string, since the error message might be too long.
Any ideas?
Thanks.

You create your own type of Error that holds the list of missing fields:
class MissingFieldsError extends Error {
constructor(fields, ...params) {
super(...params);
this.fields_ = fields;
}
getMissingFields() {
return this.fields_;
}
}
To use it, you can gather all of the missing fields first and then throw an instance of MissingFieldsError.
Assuming you have some sort of array of Field instances, the code should look like this:
const missingFields = fields
.filter((field) => field.isMissing())
.map((field) => field.getName());
if (missingFields.length > 0) {
throw new MissingFieldsError(
missingFields, 'Some required fields are missing.');
}
If you prefer, you can even have separate Errors thrown, collect them and then throw the MissingFieldsError:
const missingFields = [];
fields.forEach((field) => {
try {
const value = field.getValue();
// Process the field's value.
// Could be just field.validate();
} catch (e) {
missingFields.push(field.getName());
}
});
if (missingFields.length > 0) {
throw new MissingFieldsError(
missingFields, 'Some required fields are missing.');
}

You can throw any kind of object, you're not limited to strings, so you might as well collect your error messages into an object, and throw that in the end, if any errors were present.

Hey so currently dealing with an issue related to this. Just wanted to add a couple thoughts.
throw allows you to throw any expression and it will generate an exception which will kick into the catch clause if it exists with the value populated as the parameter (typically an Error). This is user-defined though so you can essentially throw whatever you want.
So if you really wanted to throw an array of exceptions, you totally can. i.e. something like this
try {
var errors = [];
for (let i=0; i<10; i++) {
errors.push(new Error('Here is a special exception'));
}
if (errors.length > 0) {
throw errors;
}
} catch(e) {
for (let i=0; i<e.length; i++) {
console.log(e[i]);
}
}
I think some things to be aware of
can your function throw different types? If you can throw arrays, numbers, exceptions, any other expressions, you'll need to handle all those cases. Generally people will move this into their own error handler, so you'll need to handle collections in this case
be careful of nested try/catch statements. You would normally need to be aware of this but throwing it out there (haha!) anyways for sanity purposes.
When handling async code where errors can get populated into the array at different times, make sure to utilize await and Promise.all before going through (unless you don't mind missing out on exceptions).
In terms of how good/bad practice it is, I honestly couldn't find much about it. I would say tread lightly and understand if your use case really needs this. Sometimes it's helpful but you may not need to process the entire array of exceptions.
Hope this helps.

I basically throw errors from my service classes or service methods with exact exception names so that later we can handle them according to the exception name like the following
try{
const user = User.findOne({ _id: request.body.user_id });
if (!user) {
throw new Error ("USER_NOT_FOUND_EXCEPTION")
}
const articles = Article.find({ user_id: user._id });
if (!articles) {
throw new Error ("USER_ARTICLES_NOT_FOUND_EXCEPTION")
}
//Rest of the code goes below
return response.status(200).json({
status: "success"
});
}catch(error) {
/** Handle specific exceptions */
if (error.message == "USER_NOT_FOUND_EXCEPTION") {
return response.status(400).json({
status: "error",
message: "User details not found."
});
}
if (error.message == "USER_ARTICLES_NOT_FOUND_EXCEPTION") {
return response.status(400).json({
status: "error",
message: "User articles not found."
});
}
/** Handle generic exceptions */
return response.status(400).json({
status: "error",
message: error.message
});
}

Related

Throwing multiple exceptions inside of a function

I'm implementing a form validation function that throws Errors. These exceptions will bubble up and be managed on a higher level inside my application:
this.form.inputs.forEach(input => {
if (input.required && !input.value) {
throw new AppError({ customMessage: new Notification(notificationTypes.Error, `${input.label} not filled`)});
}
})
The thing is throwing an exception stops the function execution, so I can only catch the first error.
Any suggestion? I am running out of ideas : (
If you want to continue checking, you need to not throw an exception. Instead, have an array of objects indicating the problem, and throw a single exception with that array:
const errors = [];
this.form.inputs.forEach(input => {
if (input.required && !input.value) {
errors.push({ customMessage: new Notification(notificationTypes.Error, `${input.label} not filled`)});
}
});
if (errors.length) {
throw new AppError(errors);
}
Maybe something like
const errs = this.form.inputs
.map(input => {
if (input.required && !input.value) {
return new AppError({ customMessage: new Notification(notificationTypes.Error, `${input.label} not filled`)});
}
})
.filter(err => err !== undefined)
Whenever an exception is thrown the execution inside that thread is aborted and flux control goes to the calling thread.
IF you want to return validation messages for all controls, you can add them to an array inside your for loop and then return those values (or throw an exception outside the for loop).
this could solve your problem
this.form.inputs.forEach(input => {
try {
if (input.required && !input.value) {
throw new AppError({ customMessage: new Notification(notificationTypes.Error, `${input.label} not filled`)});
}
}
catch (e) {
console.log(e)
}
})
please try this and comment back

Mongoose - How can I throw more than one error in pre (save/update) middleware?

I have some pre save and update hooks in a model and I need to show all the errors of validations at same time.
There is in the documentation this information about next function:
Calling next() multiple times is a no-op. If you call next() with an error err1 and then throw an error err2, mongoose will report err1.
See the reference here
I want to do something like the code below to return two or more validation errors, but like the documentation only the first error is thrown
Schema.pre('save', function(next) {
if (this.prop1 == 'foo')
next(new Error('Error one'))
if (this.prop2 == 'bar')
next(new Error('Error two'))
})
How can I do it? Is there an alternative?
You can add your errors into an array, and then at the end if the length of the array is greater than 0, you can send one error message by joining errors.
Schema.pre("save", function(next) {
let validationErrors = [];
if (this.prop1 == "foo") validationErrors.push("Error one");
if (this.prop2 == "bar") validationErrors.push("Error two");
if (validationErrors.length > 0) {
next(new Error(validationErrors.join(",")));
}
next();
});
But generally we don't use this kind of validation. If you are already using mongoose, you can use its validation features.
Some of the other validation packages are:
Express Validator
Joi
Hello Daniel welcome to Stack Overflow!
My approach would be to save the errors into an iterable and send down the iterable as the error object (or stringify with join() the whole thing if it accepts strings only.
Please check if this handles your issue, I don't know of any built-in ways of achieving your end result.
Schema.pre('save', function(next) {
const errors = [];
if (this.prop1 == 'foo')
errors.push('Error one');
if (this.prop2 == 'bar')
errors.push('Error two');
if (this.prop1 == 'foo' || this.prop2 == 'bar') next(new Error(errors))
})

Make SQLite errors specific and not just SQLITE_ERROR in express/node.js

So basically I have a similar block written in Express:
var sqlite3 = require('sqlite3');
const data = {};
const db = new sqlite3.Database("sqlite/test.db");
db.run("INSERT INTO [Employees]([Name]) VALUES(?)", [name], function (err) {
if (err) { //this is the error block
data.error = err.code;
data.status = 0;
}
else { //this is the success block
data.data = { Name: name, Id: this.lastID };
data.status = 1;
}
db.close();
res.send(data);
});
This does what it looks like, just a simple insert statement and its callback. The update, select and delete operations are literally all the same.
But whenever I get errors (syntax error, missing fields, missing tables, yada yada), I always just get {errNo: 0, code: "SQLITE_ERROR"} in my err argument, which is annoyingly unspecific and unhelpful.
Is there a way to fix the error handling and make it not insanity inducing?
This is the closest thing I found to documentation on the error object. You probably want to look at err.message.

Break the loop/promise and return from function

I have following code snippet.
this.clickButtonText = function (buttonText, attempts, defer) {
var me = this;
if (attempts == null) {
attempts = 3;
}
if (defer == null) {
defer = protractor.promise.defer();
}
browser.driver.findElements(by.tagName('button')).then(function (buttons) {
buttons.forEach(function (button) {
button.getText().then(
function (text) {
console.log('button_loop:' + text);
if (text == buttonText) {
defer.fulfill(button.click());
console.log('RESOLVED!');
return defer.promise;
}
},
function (err) {
console.log("ERROR::" + err);
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1, defer);
} else {
throw err;
}
}
);
});
});
return defer.promise;
};
From time to time my code reaches 'ERROR::StaleElementReferenceError: stale element reference: element is not attached to the page document' line so I need to try again and invoke my function with "attempt - 1" parameter. That is expected behaviour.
But once it reaches "RESOLVED!" line it keeps iterating so I see smth like this:
button_loop:wrong_label_1
button_loop:CORRECT_LABEL
RESOLVED!
button_loop:wrong_label_2
button_loop:wrong_label_3
button_loop:wrong_label_4
The question is: how to break the loop/promise and return from function after console.log('RESOLVED!'); line?
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead.
SOURCE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
Out of curiosity what are you trying to accomplish? To me it seems like you want to click a button based on it's text so you are iterating through all buttons on the page limited by an attempt number until you find a match for the text.
It also looks like you are using protractor on a non-angular page it would be easier if you used browser.ignoreSynchronization = true; in your spec files or the onPrepare block in the conf.js file so you could still leverage the protractor API which has two element locators that easily achieve this.
this.clickButtonText = function(buttonText) {
return element.all(by.cssContainingText('button',buttonText)).get(0).click();
};
OR
this.clickButtonText = function(buttonText) {
return element.all(by.buttonText(buttonText)).get(0).click();
};
If there is another reason for wanting to loop through the buttons I could write up a more complex explanation that uses bluebird to loop through the elements. It is a very useful library for resolving promises.
You are making it harder on yourself by creating an extra deferred object. You can use the promises themselves to retry the action if the click fails.
var clickOrRetry = function(element, attempts) {
attempts = attempts === undefined ? 3 : attempts;
return element.click().then(function() {}, function(err) {
if (attempts > 0) {
return clickOrRetry(element, attempts - 1);
} else {
throw new Error('I failed to click it -- ' + err);
}
});
};
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.forEach(function(button) {
return clickOrRetry(button);
});
});
One approach would be to build (at each "try") a promise chain that continues on failure but skips to the end on success. Such a chain would be of the general form ...
return initialPromise.catch(...).catch(...).catch(...)...;
... and is simple to construct programmatically using the javascript Array method .reduce().
In practice, the code will be made bulky by :
the need to call the async button.getText() then perform the associated test for matching text,
the need to orchestrate 3 tries,
but still not too unwieldy.
As far as I can tell, you want something like this :
this.clickButtonText = function (buttonText, attempts) {
var me = this;
if(attempts === undefined) {
attempts = 3;
}
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.reduce(function(promise, button) {
return promise.catch(function(error) {
return button.getText().then(function(text) {
if(text === buttonText) {
return button.click(); // if/when this happens, the rest of the catch chain (including its terminal catch) will be bypassed, and whatever is returned by `button.click()` will be delivered.
} else {
throw error; //rethrow the "no match" error
}
});
});
}, Promise.reject(new Error('no match'))).catch(function(err) {
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1); // retry
} else {
throw err; //rethrow whatever error brought you to this catch; probably a "no match" but potentially an error thrown by `button.getText()`.
}
});
});
};
Notes :
With this approach, there's no need to pass in a deferred object. In fact, whatever approach you adopt, that's bad practice anyway. Deferreds are seldom necessary, and even more seldom need to be passed around.
I moved the terminal catch(... retry ...) block to be a final catch, after the catch chain built by reduce. That makes more sense than an button.getText().then(onSucccess, onError) structure, which would cause a retry at the first failure to match buttonText; that seems wrong to me.
You could move the terminal catch even further down such that an error thrown by browser.driver.findElements() would be caught (for retry), though that is probably overkill. If browser.driver.findElements() fails once, it will probably fail again.
the "retry" strategy could be alternatively achieved by 3x concatenatation of the catch chain built by the .reduce() process. But you would see a larger memory spike.
I omitted the various console.log()s for clarity but they should be quite simple to reinject.

Execute mongoose queries in order and return first that found something successfully

StopsModel.findOne({stop_lat: stations[0]}).exec()
.then(function(stop){
if (stop) res.json(stop);
else {
StopsModel.findOne({stop_lat: stations[1]}).exec()
.then(function(stop){
if (stop) res.json(stop);
else {
StopsModel.findOne({stop_lat: stations[2]}).exec()
.then(function(stop){
if (stop) res.json(stop);
else {
StopsModel.findOne({stop_lat: stations[3]}).exec()
.then(function(stop){
if (stop) res.json(stop);
})
}
})
}
})
}
})
I'm using mongoose and bluebird, what I'm trying to do here is go through an array of latitudes in order and send a response back to the front end with the first one from the array that exists in the database, not the first query that returns successfully.
I know that bluebird has many promise resolution techniques, like .any .all .some .settle, but I can't figure out which one to use that will work identically to the code above.
You could simply use $in operator to get all and then simply choose one from returned array. Also it means only one db query instead of 4.
StopsModel.find({stop_lat: {$in: stations}}, function(err, stops){
if(!err && stops.length) {
return res.json(stops[0]);
// to get random stop use
// return res.json(stops[Math.random(0, stops.length - 1)]);
}
res.json([]);
});
#Molda's answer looks like it might be the way to go but for the record, the code in the question can be mechanised for a stations array of any length by building a .catch() chain as follows :
stations.reduce(function(promise, station) {
return promise.catch(function() {
return StopsModel.findOne({'stop_lat': station}).exec().then(function(stop) {
if(stop) {
return stop; // success!! break out of the catch chain
} else {
throw new Error(); // continue down the catch chain
}
});
});
}, Promise.reject()).then(function(stop) {
res.json(stop);
}, function(error) {
// no matching stop was found.
// do something with the error.
});
As long as errors are thrown, the chain will continue with another .findOne().exec() request.
Despite its relative inefficiency, there may actually be an advantage in doing it this way in that any bad stations are less likely to scupper the entire enterprise. In addition to the deliberately thrown error, any .findOne().exec() errors will also be caught and the chain will continue.

Categories

Resources