I'm currently implementing a PDF viewer based on PDF.js and as part of that I learned about promise objects.
I also learned that runtime errors are not automatically shown in the debugging console:
PDFJS.getDocument(...).then(
function(pdfDocument){
alert(UndefinedVariable); // Not shown in console!
},
function(error){
console.log("Error occurred", error);
}
);
I haven't been able to find a pretty way to show runtime errors in the promise functions, other than adding .done() as described in http://www.asyncdev.net/2013/07/promises-errors-and-express-js/ (which doesn't work for PDF.js) or adding .catch(function(error){ console.error(error); }).
I know that I can break on exceptions from runtime errors in the debugger, but I also get breaks on other exceptions (in jQuery) by doing so, which means I have to step thorugh 5 jQuery exceptions on every page load, before I can even check if my own code contains runtime errors.
Is there any way to force the promise functions to log runtime errors like normal (without writing extra code for every function call)?
The problem you're experiencing is that an exception in the then callback does reject the promise returned by .then(), instead of calling the error handler that you passed in. That will only trigger for errors in the promise on which you called .then(). So you can chain your handlers:
PDFJS.getDocument(...).then(function(pdfDocument){
alert(UndefinedVariable); // Now shown in console!
}).then(null, function(error){
console.log("Error occurred", error);
});
Here, then(null, …) could also be abbreviated by catch(…).
If there is no done method that throws on errors, you could implement it yourself like this by throwing in a setTimeout.
Is there any way to force the promise functions to log runtime errors like normal (without writing extra code for every function call)?
No. That's just not how they were designed.
In the Promise implementation, there is a try ... catch which takes an error from the callbacks and turns it into an error returned by the Promise.
One thing you can do is change that try...catch to log the errors before invoking the failure of the promise.
https://github.com/mozilla/pdf.js/blob/master/src/shared/util.js#L936
} catch (ex) {
console.error(ex); // <--- add this line
nextStatus = STATUS_REJECTED;
nextValue = ex;
}
If native ECMAScript 6 promises are used instead, this trick probably won't work.
Related
This question already has answers here:
When is .then(success, fail) considered an antipattern for promises?
(7 answers)
Closed 4 years ago.
I have come across multiple applications where using catch is preferred over rejectHandler.
Eg:
Preferring
new Promise.then(resolveHandler).catch()
instead of
new Promise().then(resolveHandler, rejectHandler).catch()
Is there a particular reason for this??
I find
new Promise().then(resolveHandler, rejectHandler).catch()
to be more useful because
I can use rejectHandler to address designed/expected error scenario where Promise.reject is called.
I can use catch block to address unknown/unexpected programming/runtime errors that occur.
Does someone know any particular reason why rejectHandler is not used much?
P.S. I am aware of newer alternatives in ES6 but I just curious to know this.
Update: I KNOW HOW rejectHandler and catch works. The question is why do I see more people use only catch over both rejectHandler and catch? Is this a best practice or there is some advantage?
Update(Adding answer here): Found the answer I was looking for first hand.
The reason is not just because the error in reject is handled by catch it is mainly because of chaining. When we are chaining promise.then.then.then.then, having a resolve, reject pattern proves a bit tricky to chain it since you wouldn't want to implement a rejecthandler just to forward the rejectData up the chain. Using only promise/then/catch along with resolve/return/throw proves very useful in chaining N numbers of thenables.
#Bob-Fanger(accepted answer) addressed some part of this too.
Eg:
getData(id) {
return service.getData().then(dataList => {
const data = dataList.find(data => {
return data.id === id;
});
if (!data) {
// If I use Promise.reject here and use a reject handler in the parent then the parent might just be using the handler to route the error upwards in the chain
//If I use Promise.reject here and parent doesn't use reject handler then it goes to catch which can be just achieved using throw.
throw {
code: 404,
message: 'Data not present for this ID'
};
}
return configuration;
});
}
//somewhere up the chain
....getConfiguration()
.then(() => {
//successful promise execution
})
.catch(err => {
if (err.code) {
// checked exception
send(err);
} else {
//unchecked exception
send({
code: 500,
message: `Internal Server error: ${err}`
});
}
});
Using just these All I need to worry about is promise/then/catch along with resolve/return/throw anywhere in the chain.
The difference is that if an error occurs inside resolveHandler it won't be handled by the rejectHandler, that one only handles rejections in the original promise.
The rejectHandler is not used in combination with catch that much, because most of the time we only care about that something went wrong.
Creating only one errorhandler makes the code easier to reason about.
If a specific promise in the chain should handled differently that can be a reason to use a rejectHandler, but i'd probably write a catch().then().catch() in that case.
Neither is more useful than the other. Both the rejected handler and the catch callback are called when an error is thrown or a promise is rejected.
There is no "best practice" to use one over the other. You may see code use one or the other, but it's use will be based on what the code needs to achieve. The programmer may want to catch an error at different times in the chain and handle errors thrown at different times differently.
Hopefully the following will help explain what I mean:
somePromise
.then(
function() { /* code when somePromise has resolved */ },
function() {
/* code when somePromise has thrown or has been rejected.
An error thrown in the resolvedHandler
will NOT be handled by this callback */ }
);
somePromise
.then(
function() { /* code when somePromise has resolved */ }
)
.catch(
function() {
/* code when somePromise has thrown or has been rejected OR
when whatever has occurred in the .then
chained to somePromise has thrown or
the promise returned from it has been rejected */ }
);
Notice that in the first snippet, if the resolved handler throws then there is no rejected handler (or catch callback) that can catch the error. An error thrown in a resolved callback will not be caught by the rejectedHandler that is specified as the second argument to the .then
As stated in the post, provision of a resolve and reject handler in the same call to .then allows dealing with rejection of the previous promise separately from errors thrown within, or returning a rejected promise from, the success handler.
Because a rejection handler returning without throwing an error resumes the fufilled channel of a promise chain, a final catch handler will not be invoked if a previous rejection handler returns normally.
The question then devolves into use cases, cost of development and level of knowledge.
Use cases
In theory the two parameter form of then call could be used to retry an operation. But because hard coded promise chains are set up statically, retrying the operation is not simple. An easier way to retry might be to use an async function with try-catch statements surrounding await of a promise that may need to be retried as in this concept code:
async function() {
let retries = 3;
let failureErr = null;
while( retries--) {
try {
var result = await retryableOperationPromise()
return result;
}
catch( err) {
failureErr = err;
}
}
throw failureErr // no more retries
}
Other use cases may not be widespread.
Cost of development or commercial decisions.
If telling a user to retry later is acceptable it may be cheaper than doing anything about specific reasons for promise rejection. For example if I try to book a flight over midnight, when airlines put the prices up, I usually get told "an error occurred, try again later" because the price I was given at the start of booking will not be honored.
Knowledge (<opinion>)
I suspect that promise usage may often be based on example rather than in-depth knowledge of the subject. It is also possible program managers want to keep the code base as simple as possible for less experienced developers (probably a cost issue).
"Best practice" may not truly apply for making decisions on how to use promises if the usage is valid. Some developers and managers will avoid some forms of usage as a matter of principle, but not always based on technical merit.
I have a problem with promises. It is extremely hard for me to differentiate between the type of error that I would want to crash from and the error I want to handle.
This is how I think I should solve these types of errors but I would like to know if it is an elegant way of doing so.
PromiseA //I believe that any errors thrown in this function will crash node
.catch(function( {throw custom_err(msg)}))
//Here I can catch any rejected promises which I believe will always be
//operational errors and then I can flag them as such.
//The error is then thrown to the final catch which handles it appropriately
.then(function() { return Function1().catch(throw custom_err(msg)); })
//Now if an error is thrown by Function1 inside a then statement it will
//fall down the chain. It will then be caught by the final catch and it will not be flagged
//as operational. My final catch will throw it and crash node which I desire.
//However if it is operational Function1 will reject the promise and it will be caught by
//the nested catch which then flags it appropriately and throws to the final catch
.catch(finalErrorHandler);
Does this solution work? Am I correct with my assumptions about the flow of rejections vs. throws?
Am I correct in assuming that if an error is thrown from a function in a library I am using (such as Function1()) it is most likely a programmer error?
Is this an elegant way of solving the problem? Is there a more obvious solution I am missing?
I was wondering whether there's a way to run a function in JavaScript and let the program ignore it if there's an error running the function?
player.play('./sounds/throughQueue.mp3', function(err){
if (err) throw err
})
Pretty much like this without the "throw err".
The program should just continue.
Looking forward to your answers.
As you mentioned in comments you want to know how to handle async function error;
You could do something like this:
function myAsyncFunction(){
return new Promise((resolve, reject) => {
// do some async anction that returns error
foo.asyncCall((err, item) => err && reject(err))
}
}
myAsyncFunction().catch(e => console.error(e))
If you observe code above async error was handled in two styles:
Callback function usually has 1st arg returning error and you can do if (err) and add any logic you want in there. Or just ignore it.
Within Promise you can use reject which is second argument in constructor. And when you reject error, you can use .catch method upon that Promise and use any custom logic you want, either new promise call or function callback.
Note I used console.error within callback function, you could just do nothing there and it will be ignored (your program will continue)
Yes, you can ignore callback/promise error or swallow try/catch caught error, but it can only make the execution go on, not make the behavior or result correctness.
If in the callback function, you can ignore error with empty error handling and the execution won't break either, but the behavior is still dependent on if there is error in the invocation.
If in promise then, you can also ignore errors caught.
If in async/await way, you must try/catch the invocation and swallow the caught err.
In a word, I suggest you handle the possible errors. after all, quality software is what we want. and what maybe happen will happen eventually.
I had a look at the bluebird promise FAQ, in which it mentions that .then(success, fail) is an antipattern. I don't quite understand its explanation as for the try and catch.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
It seems that the example is suggesting the following to be the correct way.
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
What's the difference?
What's the difference?
The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside success.
Here's a control flow diagram:
To express it in synchronous code:
// some_promise_call().then(logger.log, logger.log)
then: {
try {
var results = some_call();
} catch(e) {
logger.log(e);
break then;
} // else
logger.log(results);
}
The second log (which is like the first argument to .then()) will only be executed in the case that no exception happened. The labelled block and the break statement feel a bit odd, this is actually what python has try-except-else for (recommended reading!).
// some_promise_call().then(logger.log).catch(logger.log)
try {
var results = some_call();
logger.log(results);
} catch(e) {
logger.log(e);
}
The catch logger will also handle exceptions from the success logger call.
So much for the difference.
I don't quite understand its explanation as for the try and catch
The argument is that usually, you want to catch errors in every step of the processing and that you shouldn't use it in chains. The expectation is that you only have one final handler which handles all errors - while, when you use the "antipattern", errors in some of the then-callbacks are not handled.
However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened - i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
That you had to repeat your callback. You rather want
some_promise_call()
.catch(function(e) {
return e; // it's OK, we'll just log it
})
.done(function(res) {
logger.log(res);
});
You also might consider using .finally() for this.
The two aren't quite identical. The difference is that the first example won't catch an exception that's thrown in your success handler. So if your method should only ever return resolved promises, as is often the case, you need a trailing catch handler (or yet another then with an empty success parameter). Sure, it may be that your then handler doesn't do anything that might potentially fail, in which case using one 2-parameter then could be fine.
But I believe the point of the text you linked to is that then is mostly useful versus callbacks in its ability to chain a bunch of asynchronous steps, and when you actually do this, the 2-parameter form of then subtly doesn't behave quite as expected, for the above reason. It's particularly counterintuitive when used mid-chain.
As someone who's done a lot of complex async stuff and bumped into corners like this more than I care to admit, I really recommend avoiding this anti-pattern and going with the separate handler approach.
By looking at advantages and disadvantages of both we can make a calculated guess as to which is appropriate for the situation.
These are the two main approaches to implementing promises. Both have it's pluses and minus
Catch Approach
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
Advantages
All errors are handled by one catch block.
Even catches any exception in the then block.
Chaining of multiple success callbacks
Disadvantages
In case of chaining it becomes difficult to show different error messages.
Success/Error Approach
some_promise_call()
.then(function success(res) { logger.log(res) },
function error(err) { logger.log(err) })
Advantages
You get fine grained error control.
You can have common error handling function for various categories of errors like db error, 500 error etc.
Disavantages
You will still need another catch if you wish to handler errors thrown by the success callback
Simple explain:
In ES2018
When the catch method is called with argument onRejected, the
following steps are taken:
Let promise be the this value.
Return ? Invoke(promise, "then", « undefined, onRejected »).
that means:
promise.then(f1).catch(f2)
equals
promise.then(f1).then(undefiend, f2)
Using .then().catch() lets you enable Promise Chaining which is required to fulfil a workflow. You may need to read some information from database then you want to pass it to an async API then you want to manipulate the response. You may want to push the response back into the database. Handling all these workflows with your concept is doable but very hard to manage. The better solution will be then().then().then().then().catch() which receives all errors in just once catch and lets you keep the maintainability of the code.
Using then() and catch() helps chain success and failure handler on the promise.catch() works on promise returned by then(). It handles,
If promise was rejected. See #3 in the picture
If error occurred in success handler of then(), between line numbers 4 to 7 below. See #2.a in the picture
(Failure callback on then() does not handle this.)
If error occurred in failure handler of then(), line number 8 below. See #3.b in the picture.
1. let promiseRef: Promise = this. aTimetakingTask (false);
2. promiseRef
3. .then(
4. (result) => {
5. /* successfully, resolved promise.
6. Work on data here */
7. },
8. (error) => console.log(error)
9. )
10. .catch( (e) => {
11. /* successfully, resolved promise.
12. Work on data here */
13. });
Note: Many times, failure handler might not be defined if catch() is
written already.
EDIT: reject() result in invoking catch() only if the error
handler in then() is not defined. Notice #3 in the picture to
the catch(). It is invoked when handler in line# 8 and 9 are not
defined.
It makes sense because promise returned by then() does not have an error if a callback is taking care of it.
Instead of words, good example. Following code (if first promise resolved):
Promise.resolve()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
is identical to:
Promise.resolve()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
But with rejected first promise, this is not identical:
Promise.reject()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
Promise.reject()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
I've heard from plenty of people saying that throwing errors in Node is bad practice, and you should rather manually handle them via CommonJS's callback syntax:
somethingThatPassesAnError( function(err, value) {
if (err) console.log("ERROR: " + err);
});
Yet, I've found in multiple unit testing frameworks (Mocha, Should.js, Gently) that it seems like they want you to throw an error when something happens. I mean, sure, you can design your tests to check for equality of variables and check for not-null in error vars, but in the words of Ryan Dahl himself, "you should write your framework to make the right things easy to do and the wrong things hard to do".
So what gives? Can anyone explain why that practice exists? Should I start throwing fatal exceptions like require() would if the module couldn't be found?
It because nodejs programs typically make heavy use of async, and as a result errors are often thrown after your try/catch has already completed successfully. Consider this contrived example.
function foo(callback) {
process.nextTick(function() {
if (something) throw "error";
callback("data");
});
}
try {
foo(function(data) {
dosomething(data);
});
} catch (e) {
// "error" will not be caught here, as this code will have been executed
// before the callback returns.
}
The typical node pattern, of the first argument in a callback being an error, obviates this problem, providing a consistent way to return errors from asynchronous code.
function foo(callback) {
process.nextTick(function() {
if (something) return callback("error");
callback("data");
});
}
foo(function(error, data) {
if (error) return handleError(error);
dosomething(data);
});
It is my understanding that the case against throwing exceptions in JavaScript is due to the heavy use of asynchronous patterns. When an error occurs on another stack, you can't catch it. In those cases, use the err parameter as the first parameter for the callback.
I don't think that is the same as saying "never throw anything". If I have synchronous code, and an exception occurs, I throw it. There are differing opinions, but if callbacks aren't involved at all, I see no reason to not use throw.
I tend to follow the guidance on Joyent's Error Handling in Node.js for this one. The oversimplified gist is that there are really two types of errors (operational and programmer), and three ways to pass errors (emitting an error event on an event emitter, returning the callback with the error argument as non-null, and throwing the error).
Operational errors are errors that you expect might happen and are able to handle, ie. not necessarily bugs. Programmer errors are errors in the code itself. If you are writing code and you expect the error, then any of the patterns for passing an error are valuable. For example:
If the error happens inside an asynchronous function that accepts a callback, using the idiomatic return callback(new Error('Yadda yadda yadda')) is a correct solution (if you can't handle the error in the function).
If the error happens inside of a synchronous function and is a breaking problem (ie. the program cannot continue without the operation that was attempted) then blowing up with an uncaught thrown error is acceptable.
If the error occurs in a synchronous function but can be dealt with, then the error should be dealt with, otherwise it should be thrown, and maybe the parent function can handle it, maybe not.
Personally, I tend to only throw errors that I consider fatal, thus my code is mostly devoid of try/catch blocks (I even wrap JSON.parse in a function defined thusly: function jsonParseAsync(json, cb) { var out, err; try { out = JSON.parse(json) } catch(e) { err = e }; return cb(err, out); } ). I also try to avoid promises because they conflate promise rejection and thrown errors (even though this is getting harder to do as promises become more ubiquitous). Instead, I tend to think of synchronous functions as mathematical proofs in that if they are correct they must always be correct (thus an error in a synchronous function should break the whole program, otherwise the proof can be wrong but still usable). My error creation and checks are almost entirely assertions for bad input and emitting error events or handling asynchronous errors idiomatically.
I would suggest using exceptions to handle critical errors, much like the way require() works. If this functionality causes Node.js to misbehave, then that's a bug which I'm sure will get fixed in time.