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>
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
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...
}
The code is a part of a much bigger and complicated code, so I am just gonna put the relevant snippets to my question.
I have this promise.all snippet:
Promise.all(receivedObjs.arrayCMsIds.map(cmid =>
server.writeAttachedCMinfo(invId,cmid)))
.then(function (results) { // for promise all
return res.json(apiHelp.success(results,"success"));
}).catch(function (error) {
res.json(apiHelp.error(error, error));
});
And this long complicated writeAttachedCMinfo function:
server.writeAttachedCMinfo = function (invId,cmid) {
return new Promise(function (resolve, reject) {
console.log("writeAttachedCMinfo");
console.log("invoiceId " + invId);
console.log("cmid "+ cmid);
var invoiceId = JSON.stringify(invId);
var cmId = JSON.stringify(cmid);
var invIdString = invoiceId;
var cmIdString = cmId;
invIdString = invIdString.slice(1, -1);
cmIdString = cmIdString.slice(1, -1);
var projection = 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos';
ubiqInvoice.findById(invIdString, projection).then(function (dbInvoice) {
var intInvCertifiedAmount = parseInt(dbInvoice.certifiedInvoiceAmount);
creditMemo.findById(cmIdString).then(function (dbCreditMemo) {
var intCreditMemoAmount = parseInt(dbCreditMemo.creditMemoAmount);
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
if (dbCreditMemo.isAssociated) {
return new Error ('CMisAssociated');
}
if (dbInvoice.gwCode === "100000000000"
|| dbInvoice.gwCode === "110000000000"
|| dbInvoice.gwCode === "111200000000"
|| dbInvoice.gwCode === "111100000000"
|| dbInvoice.gwCode === "111110000000"
) {
var creditMemoEntry = {
id: guid.create().value,
batchId: dbCreditMemo.batchId,
invoiceId: dbInvoice._id,
recordTypeCode: "CM",
buyerReference: dbInvoice.buyerReference,
supplierReference: dbInvoice.supplierReference,
creditMemoNo: dbCreditMemo.creditMemoNo,
creditMemoIssuingDate: dbCreditMemo.creditMemoIssuingDate,
creditMemoEffectiveDate: dbCreditMemo.creditMemoEffectiveDate,
lastModificationDate: dbCreditMemo.lastModificationDate,
currencyCode: dbCreditMemo.currencyCode,
creditMemoAmount: dbCreditMemo.creditMemoAmount,
hashCode: dbCreditMemo.hashCode,
description: dbCreditMemo.description,
uploadDate: dbCreditMemo.uploadDate,
isAssociated: true,
}
dbInvoice.creditMemos.push(creditMemoEntry);
dbInvoice.certifiedInvoiceAmount = dbInvoice.certifiedInvoiceAmount - dbCreditMemo.creditMemoAmount;
dbInvoice.save();
dbCreditMemo.isAssociated = true;
dbCreditMemo.save();
resolve(dbInvoice)
}
else { return new Error ('wrongggwcode'); }
})
});
}), function (error) {
console.log("error: " + error);
}
}
My goal, is to force error throwing in case one of the if conditions aren't met, and I want to pass the error to client in a form of a custom message so i can use it on the client said for displaying various errors , such as 'CMisbiggerThanInvoice'
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
I am just trying to figure out a way to pass the error from the writeAttachedCMinfo function to the promise.all's .catch(function (error) but it's not working, the promise.all is always returning success even if one of the if conditions aren't met.
I have tried reject('CMisbiggerThanInvoice'), reject(new Error('CMisbiggerThanInvoice')...all the same.
how can i really force the promise function to return an error?
In the context of a promise you should actually throw the error:
throw new Error('wrongggwcode');
If this executes in a promise constructor callback or then callback, it can be caught via the catch method (or second argument of then) and the argument of the callback you pass to it will be the error (object).
Calling reject from within a then callback will obviously not work, since you don't have access to reject there, but it will work in a promise constructor callback.
Simple example:
new Promise((resolve, reject) => {
setTimeout( () => reject(new Error('this is an error')) );
}).then( (value) => console.log('resolved with ' + value) )
.catch( (error) => console.log('error message: ', error.message) );
Nesting
When you have nested promises within then callbacks, make sure you always return the value returned by the inner promise as the return value of the outer then callback.
So in your case do this:
return creditMemo.findById( ....
//^^^^
For the same reason you need to do:
return ubiqInvoice.findById( ....
//^^^^
It would lead to far for this question/answer, but it is best practice to avoid nesting promise then calls all together. Instead of calling then on a nested promise, just return the promise without the then call, and apply that then call one level higher, so that you have a "flat" chain of then calls. This is just a matter of best practice, although it should also work like you have done it provided you always return the inner promises.
Position of the error handler
The error handler is placed at a wrong position; in fact you are using the comma operator. In short, you have this in your code:
new Promise(function (resolve, reject) {
// ... //
}), function (error) {
console.log("error: " + error);
}
The function after the comma is never executed as it is not an argument to a method call. It just sits behind a comma operator.
What you want is a catch method call on the new promise, and cascade the error in there so that Promise.all will also receive the rejection:
return new Promise(function (resolve, reject) {
// ... //
}).catch(function (error) {
console.log("error: " + error);
throw error; // cascade it
});
In server.writeAttachedCMinfo() the main things are :
to get rid of the new Promise() wrapper - it's not necessary - (Promise constructor anti-pattern).
to throw errors, rather than return them.
Also, because there's only one invId, there's effectively only one dbInvoice and it needn't be retrieved over and over from the database on each call of server.writeAttachedCMinfo(). In fact it shouldn't be retrieved over and over, otherwise each dbInvoice.save() may well overwrite earlier saves within the same overall transaction. It will be safer to accumulate all creditMemos and progressively decrement the certifiedInvoiceAmount in a single dbInvoice object, and evetually perform a single `dbInvoice.save().
server.writeAttachedCMinfo = function(dbInvoice, cmid) {
return creditMemo.findById(JSON.stringify(cmid).slice(1, -1))
.then(dbCreditMemo => {
if(parseInt(dbInvoice.certifiedInvoiceAmount) <= parseInt(dbCreditMemo.creditMemoAmount)) {
throw new Error('CMisbiggerThanInvoice');
// ^^^^^
}
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
return dbCreditMemo; // deliver dbCreditMemo via returned Promise
});
}
Now, in the caller :
ubiqInvoice.findById() can be called once.
perform checks on dbInvoice, and throw on failure.
pass dbInvoice, rather than invId to server.writeAttachedCMinfo() at each call.
all saving can be done here, not in server.writeAttachedCMinfo().
As a consequence, the caller subsumes some of the code that was in server.writeAttachedCMinfo() :
ubiqInvoice.findById(JSON.stringify(invId).slice(1, -1), 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos')
.then(dbInvoice => {
if(dbCreditMemo.isAssociated) {
throw new Error('CMisAssociated');
// ^^^^^
}
if(dbInvoice.gwCode === '100000000000'
|| dbInvoice.gwCode === '110000000000'
|| dbInvoice.gwCode === '111200000000'
|| dbInvoice.gwCode === '111100000000'
|| dbInvoice.gwCode === '111110000000'
) {
return Promise.all(receivedObjs.arrayCMsIds.map(cmid => {
return server.writeAttachedCMinfo(dbInvoice, cmid)
.catch(error => {
console.log(error);
return null;
});
}))
.then(dbCreditMemos => {
return Promise.all(dbCreditMemos.map(memo => {
return memo ? memo.save() : null;
}))
.then(() => dbInvoice.save())
.then(() => {
res.json(apiHelp.success(dbInvoice, 'success'));
});
});
} else {
throw new Error('wrongggwcode');
// ^^^^^
}
})
.catch(error => {
console.log('error: ' + error);
res.json(apiHelp.error(error, error));
});
The whole area around catching/handling errors arising from server.writeAttachedCMinfo() requires more thought. On the one hand, you might want successful sub-transactions to be saved and errors to be swallowed (as coded above) while on the other hand, you might want any single failure to cause nothing to be saved.
Another consideration is whether server.writeAttachedCMinfo() calls should be made sequentially, which would control the priority by which the sub-transactions access the credit balance. As it stands, with parallel requests, it's a bit of a free-for-all.
That addresses a bit more than the question asked for but hopefully it will be useful.
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.
As a novice in Javascript, I'm confused on which could be the best way to differentiate between the result computed by an asynchronous function, and any exception/error.
If I'm right, you cannot use try-catch in this scenario, as the called function
ends before the callback, and it is this latter who actually may throw an exception.
Well.
I've seen so far some library functions expecting a callback like: function(err, result).
So, one have to test err before using result.
Also I tried myself to return either the actual result or an Error object.
Here, the callback is of the form function(result)
and you have to test result instanceof Error before using it.
It follows an example of this:
function myAsyncFunction ( callBack ) {
async_library_function( "some data", function (err, result) {
if (err) { callBack ( new Error ("my function failed") ); return; }
callBack ( some calculation with result );
});
} // myFunction ()
//
// calling myFunction
//
myAsyncFunction ( function (result) {
if (result instanceof Error ) { log ("some error occurred"); return; }
log ("Alright, this is the result: " + result);
});
What is the best (maybe the common) way to do this?
Thanks.
There are three main approaches that I've been using myself:
Having an "error" parameter passed to the callback.
Having an "error" callback. This is usually combined with (1).
Having some sort of global exception manager.
I'll start with the third one. The idea is to have an object that will allow dispatching errors as well as catching them globally. Something like this:
var ErrorManager = {
subscribers: [],
subscribe: function (callback) {
this.subscribers.push(callback);
},
dispatchError: function (error) {
this.subscribers.forEach(function (s) {
s.apply(window, [ error ]);
});
}
}
This is quite specific to a given situation because there's basically no easy way of tracking the origin of an error as well as it's easy to mess up with this. For example, if you need to hide a dialog box whose contents failed to load, you'd have to propagate this information (e.g. dialog box Id/element) to all the subscribers.
The above approach is good when you want to execute an action that doesn't alter (or alters an independent part) of the web application (e.g. displays a status bar or a message to a console).
The second approach basically makes a separation between successful call and a failure. For example:
$.ajax('/articles', {
success: function () { /* All is good - populating article list */ },
error: function () { /* An error occured */ }
});
In the above example, the success callback is never executed in case of a failure so if you want to have some default behavior to always trigger, you'd need to either sync between the two callbacks or have a callback that is always called (for the above example - complete).
I personally prefer the first approach - having a callback where you have an error object passed along with potential result. This eliminates problems with having to "track" the origin/context on an error as well as worrying about the clean-up procedure (default behavior). So, something like you provided:
function beginFetchArticles(onComplete) {
$.ajax('/articles', {
complete: function (xhr) {
onComplete(xhr.status != 200 ? xhr.status.toString() : null,
$.parseJSON(xhr.responseText)); /* Something a bit more secure, probably */
}
});
}
Hope this helps.
It depends vastly on your implementation. Is this a recoverable error? If it isn't, then the way you are suggesting should work just fine. If it is recoverable then you shouldn't be returning an error. You should be returning an "empty" result. Keep in mind maintainability as well. Do you want instanceof checks throughout the code? Also, I know some programmers like that JavaScript is loose with types, but you run into consistency issues when the expected object passed through can actually be unexpected. Is it a result, or an error, or even something else altogether?
That's one way to do it. Though I'd usually leave any manipulations/processing of the result to the callback function.
Another way is you can pass back both the error and result values to the callback:
callback (err, result); \\ if no error, err = null, if no result, result = null
Alternatively, you can ask for separate error and success callbacks:
function myAsyncFunction ( successCallBack, errorCallBack ) {
\* ... *\
}
And then trigger the appropriate function depending on the received response.
One approach can be like this:
function Exception(errorMessage)
{
this.ErrorMessage = errorMessage;
this.GetMessage = function()
{
return this.ErrorMessage;
}
}
function ResultModel(value, exception)
{
exception = typeof exception == "undefined"? null, exception;
this.Value = value;
this.Exception = exception;
this.GetResult = function()
{
if(exception === null)
{
return this.Value;
}
else
{
throw this.Exception;
}
}
};
And in your usage:
function myAsyncFunction ( callBack ) {
var result;
async_library_function( "some data", function (err, result) {
if (err)
{
result = new ResultModel(null, new Exception("my function failed"));
}
else
{
result = new ResultModel(some calculation with result);
}
callBack ( result );
});
}
myAsyncFunction ( function (result) {
try
{
log ("Alright, this is the result: " + result.GetResult());
}
catch(ex)
{
log ("some error occurred" + ex.GetMessage());
return;
}
});
If you want to make robust programs, you should use promises. Otherwise you have to handle 2 different kinds of errors which is pretty crazy.
Consider how to read a file as JSON without crashing the server:
var fs = require("fs");
fs.readFile("myfile.json", function(err, contents) {
if( err ) {
console.error("Cannot read file");
}
else {
try {
var result = JSON.parse(contents);
console.log(result); //Or continue callback hell here
}
catch(e) {
console.error("Invalid json");
}
}
});
With promises e.g:
var Promise = require("bluebird");
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.json").then(JSON.parse).then(function(result){
console.log(result);
}).catch(SyntaxError, function(e){
console.error("Invalid json");
}).catch(function(e){
console.error("Cannot read file");
});
Notice also how the code grows vertically like with synchronous code instead of horizontally.