tl;dr If a task can fail at multiple events e.g. API fetch, division, parsing etc, does it make sense to have multiple try-catch blocks or a single one to catch 'em all?
I have a function in which I perform two tasks.
Fetch two numbers from an API, a and b.
Perform a/b
This is a simplified version of the actual problem. I wanted to ask how to handle for exceptions as the task can fail on either of the two steps:
The fetch itself failed.
a/b resulted in an error because b = 0.
I can think of two approaches.
Option I
try {
const data = getFromAPI();
const result = data[0] / data[1];
return result;
} catch (err) {
// Catch all errors here...
}
Option II
try {
try {
const data = getFromAPI();
} catch(err) {
// Catch all API errors here..
}
const result = data[0] / data[1];
return result;
} catch (err) {
// Catch division errors here...
}
You should start with checking the data you are working with (as far as reasonably possible). After that you should only try/catch the code which can fail / when it's out of your control, nothing else. So I will give you another option.
And to answer your other question, never make nested try catch statements. It simply doesn't make sense. If different type exceptions can occur, try identifying the type of the exception (i.e. with the instanceOf method or properties on the error object) and handle it.
Option III
try {
var data = getFromAPI();
} catch (err) {
// Catch errors from the API request here...
}
if(Array.isArray(data) && !isNaN(data[0]) && !isNaN(data[1]) && data[0] > 0 && data[1] > 0) {
const result = data[0] / data[1];
return result;
}
return 0;
This is a question that the answer depends on the system, whether you want to tell the user or want to know what kind of exception was thrown instead of doing several try / catch advise you to use a switch or an if inside the catch instead of multiple nested try / catch.
try{
//your code
}catch(ex){
if(ex instanceof ReferenceError){
//handle error
}
}
you can simply use:
try {
const data = getFromAPI(); //wait for request to finish
if(typeof data !== 'object') throw('fetch error');
if(data[0] === 0 ||
data[1] === 0 ||
typeof data[0]!== 'number' ||
typeof data[1]!== 'number'
) throw('your error here');
const result = data[0] / data[1];
return result;
} catch (err) {
// Catch all errors here...
}
Related
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
i am building validation for one of form's field serverside (expressjs) and doing following actions for that:
Read data from json file
Get property from it (Array)
Check if it contains every single element of user generated array and nothing more, for example:
[1,2,3,4,5]; (json array)
[1,2,3,4,5,6] (user generated array) //must return false
[1,2,3,4,5];
[1,3,4] //must return true;
[1,2,3,4,5];
[1,2,7] //must return false;
so i am using this code for that:
const contains = (arr1, arr2) => {
arr2.every(v => arr1.indexOf(v) !== -1)
}
var match;
fs.readFile('../tags.json', 'utf8', (err, data)=>{
var JsonData = JSON.parse(data);
var tagsArray = JsonData.tags;
console.log(tagsArray)
console.log(tags)
if(tagsArray instanceof Array){
console.log('tagsArray is array')
}
if(!contains(tagsArray, tags)){
match = false
}
else{
match = true
}
console.log(match + ' blah1')
});
console.log(match + ' blah2')
if(match == false){
return res.status(409).send({
message: 'Do not provide your own tags'
});
}
but it always returns false inside fs.readFile block because it returns undefined outside fs.readFile block, so this means that contains function return undefined (i tested it)
so what is the clue for this?
Thanks!
fs.readFile is asynchronous, so any code that depends on its result (the file being read) needs to go within your callback function. (The callback function is the (err, data) => { ... } part.)
Move the console.log(match + 'blah2') and if(match == false) { ... } parts inside of the callback (after the blah1 line).
You could also look into async or use fs.readFileSync which would allow you to avoid using callback functions.
Another side point, you will want to make sure you always reach a res.send() line, i.e. when match == true in your case. Otherwise your http request will not return when match is true.
Edit:
Here's a really basic structure for express, mostly pseudocode & comments, just to illustrate callbacks:
app.post('/tags', (req, res) => {
// your setup code here
fs.readFile('../tags.json', 'utf8', (err, data) => {
console.log('readFile has finished')
// now you have heard back from readFile
// check the err and send 500 if there was a problem
// otherwise work with the file in the var data
// any other db-related stuff also goes in here, which probably
// has its own callback you need to use
db.save(data, (err) => {
// db call is done, potentially with an error
// Here you can use `res` to send http response
})
// !! again here the db is still doing its work
})
// !! anything you add here will be executed before readFile is done
console.log('readFile is probably still at work')
})
I should also point out that you want contains to return the bool value, i.e. return arr2.every(...)
You can use async/await :
async function read(){
let data = await fs.readFile(<path>);
console.log(data); //You can use data everywhere within this scope
}
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.
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.
I'm from iOS land where I've used ReactiveCocoa extensively. In RAC, there is tryMap operator which sendError when the maping operation fails.
Is there an equivalent in Rx.JS? I can mimic the similar behavior using flatMap and another Observable but that certainly seems like an overkill.
Thanks!
Brandon's answer explains a nice way to capture error and pass it down through onNext stream.
My original question was to simply end the stream and propagate Error, which is apparently very nicely implemented in RxJS by simply throwing an exception inside the map operation block. I'm guessing RxJS wraps the entire operations in try {} catch {} block and handle exceptions gracefully by sending it down Error stream.
I'm writing this as a reference for people coming from ReactiveCocoa whose current implementation doesn't allow to throw simply due to the limitation of Swift language of not allowing exceptions, which is fixed in Swift 2.
Observable.just(1)
.map(num => { if (num == 1) { throw new Error('error') } return num; })
.subscribeOnError(console.log) // prints 'error'
I assume you want it to not end the stream when a mapping operation fails? I don't think RxJS it built in, but fairly easy to write. You just need to catch the error and have tryMap basically return an Either type of construct that is either an error or the mapping result:
Rx.Observable.prototype.tryMap = function(selector) {
var mapSelector = function(value, index, obs) {
try {
return {
value: selector(value, index, obs)
};
} catch (e) {
return {
error: e
};
}
};
return this.map(mapSelector);
};
// Usage:
Rx.Observable
.of(1, 2, 3, 4, 5)
.tryMap(function(v) {
if ((v % 2) === 1) {
throw new Error("odd numbers are evil");
}
return v / 2;
})
.subscribe(function(result) {
if (result.error) {
console.log("error: " + result.error.message);
} else {
console.log("result: " + result.value);
}
});
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://rawgit.com/Reactive-Extensions/RxJS/master/dist/rx.all.js"></script>