await Promise.reject or throw error to bail out? - javascript

I am refactoring my promise chaining codes to async/await style. One of the reasons to do that is I want a single catch block to handle to all error situations (as explained here Understanding promise rejection in node.js)
My question is when I hit a sync error, should I call await Promise.reject or throw error to bail out the process?
I know either way will work but I prefer throw error. I already know I got an invalid result why should I await? Using throw to terminate the control flow immediately seems to be a better option.
I am not talking about the promise chain(the whole point of my question), so I don't think the thread JavaScript Promises - reject vs. throw answered my question.
I read the article Error Handling in Node.js I don't think it gives an answer either. But it did say
A given function should deliver operational errors either
synchronously (with throw) or asynchronously (with a callback or event
emitter), but not both. ... In general, using throw and expecting
a caller to use try/catch is pretty rare...
My async function(s) may return Promise.reject. So I am concerned about introducing 2 ways to delivering errors as that article against.
try {
let result = await aysncFunc().
if (!isResultValid(result)) { //isResultValid() is sync function
await Promise.reject('invalid result')
//or throw 'invalid result'
}
... //further processing
}
catch (error) {
...
}

It's semantically correct to use throw in promise control flow, this is generally preferable way to bail out of promise chain.
Depending on coding style, await Promise.reject(...) may be used to differentiate between real errors and expected rejections. Rejected promise with string reason is valid but throw 'invalid result' is considered style problem that may be addressed with linter rules, because it's conventional to use Error instances as exceptions.
The reason why it's important is because string exceptions can't be detected with instanceof Error and don't have message property, consistent error logging as console.warn(error.message) will result in obscure undefined entries.
// ok
class Success extends Error {}
try {
throw new Success('just a friendly notification');
} catch (err) {
if (!(err instanceof Success)) {
console.warn(err.message);
throw err;
}
}
// more or less
const SUCCESS = 'just a friendly notification';
try {
await Promise.reject(SUCCESS);
} catch (err) {
if (err !== SUCCESS)) {
console.warn(err.message);
throw err;
}
}
// not ok
try {
throw 'exception';
} catch (err) {
if (typeof err === 'string') {
console.warn(err);
} else {
console.warn(err.message);
}
throw err;
}
Since invalid result is actually an error, it's reasonable to make it one:
throw new TypeError('invalid result');
I am not talking about the promise chain(the whole point of my question), so I don't think the thread JavaScript Promises - reject vs. throw answered my question.
async function is syntactic sugar for promise chain, so all points that are applicable to promises are applicable to async as well.
There may be cases when throwing an error is not the same as rejecting a promise, but they are specific to other promise implementations like AngularJS $q and don't affect ES6 promises. Synchronous error in Promise constructor results in exception, this also isn't applicable to async.

Related

Why a promise reject is not catched within a try catch block but produces an Uncaught error?

I'm facing a promise rejection that escapes a try catch block. The rejection causes an Uncaught exception which is something I can not comprehend.
If I add a reject handler in the Promise.then or a Promise.catch, the rejection gets captured. But I was hopping try catch will work in this situation.
What is happening here?
class HttpResponse {
json() {
return Promise.reject('parse error')
}
}
function fetch() {
return new HttpResponse();
}
const res = fetch();
try {
res.json().then(json => {
alert(`json: ${json}`);
}
//,reason => {
// alert(`promise.reject ${reason}`);
//}
)
//.catch(reason => {
// alert(`promise.catch ${reason}`);
//})
} catch (e) {
alert(`catch{} from try catch: ${e}`);
}
Promises have their own mechanism of handling errors with the catch() method. A try / catch block can't control what's happening when chaining methods.
function fetch() {
return Promise.reject('parse error');
}
const res = fetch();
res.then(json => {
console.log(`json: ${json}`);
}).catch((e) => {
console.log(`catch{} from chained catch: ${e}`);
});
However, using async / await changes that. There you don't use the methods of a promise but handle errors with a try / catch block.
function fetch() {
return Promise.reject('parse error');
}
(async function() {
try {
const res = await fetch();
} catch (e) {
console.log(`catch{} from try catch: ${e}`);
}
})();
The technical reason behind this behavior is because a JavaScript promise invokes one of two callback functions for success or failure.
A promise does not emit an Error that is required for the try to work. it is not an Error (or more technically accurate) an instance of Error. It emits and event. You are encouraged to emit a new Error() if you need it to emit one. As pointed out here: https:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
It emits an event that you can set up a handler for: https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent –
Finally, await throws and Error as described in the spec: https://tc39.es/ecma262/#await-rejected
Technical reasons behind:
HostPromiseRejectionTracker is an implementation-defined abstract
operation that allows host environments to track promise rejections.
An implementation of HostPromiseRejectionTracker must complete
normally in all cases. The default implementation of
HostPromiseRejectionTracker is to unconditionally return an empty
normal completion.
https://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker
Basically javascript engines can freely implement this spec. In the case of browsers you don't get the Error captured inside the try/catch because the error is not emitted where you think it should be. But instead it's tracked with this special event that throws the error in the console.
Also on nodejs, this event causes the process to exit if you have node set to exit on unhandled exceptions.
On the other side, if you instead use async/await, the rejection is treated like an 'error' in practical terms. Meaning that the newer async/await feature behaves in a different fashion showing that it is not only syntactic sugar for Promises.
https://tc39.es/ecma262/#sec-throwcompletion
In sum, if you use Promise.then you are forced to provide a reject argument or chain it with .catch to have the rejection captured or else it will reach the console and in case of nodejs to exit the process if configured to do so (I believe new nodejs does this by default).
But if you use the newer async/await syntax you not only have a concise code (which is secondary) but a better rejection handling because it can be properly nested in a try/catch and it will behave like an standard Error.

With Javascript Promises, are there best practices regarding the use of "error" versus catch clauses?

In learning to write in JavaScript with Promises, I'm encountering two different ways of dealing with errors. One is to use a catch, as in this example:
axios.post('/someapi', {
somedata1: 'foo'
})
.then((result) => {
console.log(result);
})
.catch((exception) => {
console.log(exception);
});
The other is to have a clause in the then for the rejected case:
axios.post('/someapi', {
somedata1: 'foo',
})
.then((response) => {
console.log(response);
}, (error) => {
console.log(error);
});
Some authors seem to use one approach, other authors use the other, and it's not clear to me why. Are there situations in which it's necessary or desirable to use one or the other?
(The example above uses axios, but that's just for the purposes of providing a code example. This question is not about axios.)
With Javascript Promises, are there best practices regarding the use of “error” versus catch clauses?
There is no universal "best practice" for that question because it depends upon what specific behavior you want your code to have. As others have mentioned, you get a different behavior in a few ways.
Some authors seem to use one approach, other authors use the other, and it's not clear to me why. Are there situations in which it's necessary or desirable to use one or the other?
Yes, there are situations to use one or the other. They provide potentially different behaviors. Only if you're 200% sure that your successHandler in .then(successHandler) can never throw or return a rejected promise, would there be no meaningful difference.
As a summary:
When using p.then(successHandler).catch(errorHandler), errorHandler will get errors that occur either from a rejected p or from an error or rejection from the successHandler.
When using p.then(successHandler, errorHandler), errorHandler will be called from a rejection of p and will NOT get called from an error or rejection from the successHandler.
Different behaviors that are useful for different circumstances.
In this .catch() example below, the .catch() will catch an error that occurs (either accidentally from a coding error, a thrown exception or when returning some other promise that rejects).
Promise.resolve("hello").then(greeting => {
console.log("throwing error");
throw new Error("My .then() handler had an error");
}).catch(err => {
// will get here
console.log("Caught error in .catch()\nError was: ", err.message);
});
But, when using the second argument to .then(), that error from the .then() handler will not be caught:
Promise.resolve("hello").then(greeting => {
console.log("throwing error");
throw new Error("My .then() handler had an error");
}, err => {
// won't get here
console.log("Caught error in .catch()\nError was: ", err.message);
});
So, sometimes you want an error that might occur in the .then() handler to hit this immediate error handler and sometimes you don't want it to hit that error handler because you want that error handler to only process errors from the original promise and you have some other catch handler later in the promise chain that will deal with this error.
Recommendation
In general, I would advise that you start out with the .catch() handler because it catches more errors and does not require that there be some other .catch() handler elsewhere in the promise chain in order to be safe. Then, you switch to the .then(successHandler, errorHandler) form if you explicitly don't want this errorHandlerto be called if there's another error in the successHandler AND you have somewhere else in the promise chain where an error in the successHandler would get caught or handled.
Both the syntaxes work out of the box. But the first one has an advantage that the Catch block is able to catch an error thrown by the Promise Rejection as well as an error thrown by then block.
Here is the Example you can try in Node and you will have a better idea about it.
function x () {
return new Promise((resolve, reject) => {
return resolve('Done...');
});
}
x()
.then(response => {
console.log('RESPONSE---', response)
throw 'Oops...Error occurred...';
})
.catch(error => console.log('ERROR---', error));
x()
.then(
response => {
console.log('RESPONSE---', response)
throw 'Oops...Error occurred...';
},
error => console.log('ERROR---', error)
);

What is the proper way to handle errors in a promise?

I've been seeing a couple of different patterns for handling errors.
One is like:
let result;
try {
result = await forSomeResult(param);
} catch {
throw new Error();
}
And the other is like:
const result = await forSomeResult(param).catch(() => throw new Error());
I prefer the latter since it looks like a cleaner solution. But I've also heard talks that the first solution is better since there could be some race condition where the .catch doesn't execute before the next command is run.
I was wondering if someone had a technical answer about why one method is better than the other.
First of all, there's no reason to catch and throw or .catch() and throw unless you're going to do something else inside the catch handler or throw a different error. If you just want the same error thrown and aren't doing anything else, then you can just skip the catch or .catch() and let the original promise rejection go back to the caller.
Then, it is generally not recommended that you mix await with .catch() as the code is not as easy to follow. If you want to catch an exception from await, use try/catch around it. .catch() works, it's just not a preferred style if you're already awaiting the promise.
The one technical difference between these two styles:
async function someFunc()
let x = await fn().catch(err => {
console.log(err);
throw err;
});
// do something else with x
return something;
}
And, this:
async function someFunc()
let x;
try {
x = await fn();
} catch(err) {
console.log(err);
throw err;
}
// do something else with x
return something;
}
is if the function fn() you are calling throws synchronously (which it shouldn't by design, but could by accident), then the try/catch option will catch that synchronous exception too, but the .catch() will not. Because it's in an async function, the async function will catch the synchronous exception and turn it into a rejected promise for you automatically for the caller to see as a rejected promise, but it wouldn't get logged or handled in your .catch() handler.
One of the more beneficial cases for try/catch with await is when you have multiple await statements and you don't need to handle errors on any of them individually. Then, you can surround them with one try/catch and catch all the errors in one place.
async function someFunc(someFile) {
let fileHandle;
try {
// three await statements in a row, all using same try/catch
fileHandle = await fsp.open(someFile);
let data = await fsp.read(...);
// modify data
await fsp.write(...)
} catch(err) {
// just log and re-throw
console.log(err);
throw err;
} finally {
// close file handle
if (fileHandle) {
await fileHandle.close();
}
}
}
It depends.
Are you going to be calling multiple asynchronous functions that could error? You can wrap them all in a try/catch and perform common error handling without having to repeat yourself:
try {
result = await forSomeResult(param);
await forSomeOtherResult();
return await finalResult(result);
} catch { //catches all three at once!
throw new Error();
}
Do you only need to handle errors for this one, specific call? The .catch() pattern is fine. There is no race condition to worry about, await waits for the promise to resolve or reject, and this includes all success and failure callbacks attached to the promise. However, in your example, you're catching an error only to throw an empty one - in that case, it may be preferable to simply write this:
const result = await forSomeResult(param);
...and let the error propagate to the caller naturally.
I've seen a mixture of both styles used common enough that I think it's fine either way - they each have a particular strength.

How to throw an exception in the Promise catch method in JavaScript? [duplicate]

Why can't I just throw an Error inside the catch callback and let the process handle the error as if it were in any other scope?
If I don't do console.log(err) nothing gets printed out and I know nothing about what happened. The process just ends...
Example:
function do1() {
return new Promise(function(resolve, reject) {
throw new Error('do1');
setTimeout(resolve, 1000)
});
}
function do2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('do2'));
}, 1000)
});
}
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // This does nothing
});
If callbacks get executed in the main thread, why does the Error get swallowed by a black hole?
As others have explained, the "black hole" is because throwing inside a .catch continues the chain with a rejected promise, and you have no more catches, leading to an unterminated chain, which swallows errors (bad!)
Add one more catch to see what's happening:
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // Where does this go?
}).catch(function(err) {
console.log(err.stack); // It goes here!
});
A catch in the middle of a chain is useful when you want the chain to proceed in spite of a failed step, but a re-throw is useful to continue failing after doing things like logging of information or cleanup steps, perhaps even altering which error is thrown.
Trick
To make the error show up as an error in the web console, as you originally intended, I use this trick:
.catch(function(err) { setTimeout(function() { throw err; }); });
Even the line numbers survive, so the link in web console takes me straight to the file and line where the (original) error happened.
Why it works
Any exception in a function called as a promise fulfillment or rejection handler gets automatically converted to a rejection of the promise you're supposed to return. The promise code that calls your function takes care of this.
A function called by setTimeout on the other hand, always runs from JavaScript stable state, i.e. it runs in a new cycle in the JavaScript event loop. Exceptions there aren't caught by anything, and make it to the web console. Since err holds all the information about the error, including the original stack, file and line number, it still gets reported correctly.
Important things to understand here
Both the then and catch functions return new promise objects.
Either throwing or explicitly rejecting, will move the current promise to the rejected state.
Since then and catch return new promise objects, they can be chained.
If you throw or reject inside a promise handler (then or catch), it will be handled in the next rejection handler down the chaining path.
As mentioned by jfriend00, the then and catch handlers are not executed synchronously. When a handler throws, it will come to an end immediately. So, the stack will be unwound and the exception would be lost. That is why throwing an exception rejects the current promise.
In your case, you are rejecting inside do1 by throwing an Error object. Now, the current promise will be in rejected state and the control will be transferred to the next handler, which is then in our case.
Since the then handler doesn't have a rejection handler, the do2 will not be executed at all. You can confirm this by using console.log inside it. Since the current promise doesn't have a rejection handler, it will also be rejected with the rejection value from the previous promise and the control will be transferred to the next handler which is catch.
As catch is a rejection handler, when you do console.log(err.stack); inside it, you are able to see the error stack trace. Now, you are throwing an Error object from it so the promise returned by catch will also be in rejected state.
Since you have not attached any rejection handler to the catch, you are not able to observe the rejection.
You can split the chain and understand this better, like this
var promise = do1().then(do2);
var promise1 = promise.catch(function (err) {
console.log("Promise", promise);
throw err;
});
promise1.catch(function (err) {
console.log("Promise1", promise1);
});
The output you will get will be something like
Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }
Inside the catch handler 1, you are getting the value of promise object as rejected.
Same way, the promise returned by the catch handler 1, is also rejected with the same error with which the promise was rejected and we are observing it in the second catch handler.
I tried the setTimeout() method detailed above...
.catch(function(err) { setTimeout(function() { throw err; }); });
Annoyingly, I found this to be completely untestable. Because it's throwing an asynchronous error, you can't wrap it inside a try/catch statement, because the catch will have stopped listening by the time error is thrown.
I reverted to just using a listener which worked perfectly and, because it's how JavaScript is meant to be used, was highly testable.
return new Promise((resolve, reject) => {
reject("err");
}).catch(err => {
this.emit("uncaughtException", err);
/* Throw so the promise is still rejected for testing */
throw err;
});
According the spec (see 3.III.d):
d. If calling then throws an exception e,
a. If resolvePromise or rejectPromise have been called, ignore it.
b. Otherwise, reject promise with e as the reason.
That means that if you throw exception in then function, it will be caught and your promise will be rejected. catch don't make a sense here, it is just shortcut to .then(null, function() {})
I guess you want to log unhandled rejections in your code. Most promises libraries fires a unhandledRejection for it. Here is relevant gist with discussion about it.
Yes promises swallow errors, and you can only catch them with .catch, as explained more in detail in other answers. If you are in Node.js and want to reproduce the normal throw behaviour, printing stack trace to console and exit process, you can do
...
throw new Error('My error message');
})
.catch(function (err) {
console.error(err.stack);
process.exit(0);
});
I know this is a bit late, but I came across this thread, and none of the solutions were easy to implement for me, so I came up with my own:
I added a little helper function which returns a promise, like so:
function throw_promise_error (error) {
return new Promise(function (resolve, reject){
reject(error)
})
}
Then, if I have a specific place in any of my promise chain where I want to throw an error (and reject the promise), I simply return from the above function with my constructed error, like so:
}).then(function (input) {
if (input === null) {
let err = {code: 400, reason: 'input provided is null'}
return throw_promise_error(err)
} else {
return noterrorpromise...
}
}).then(...).catch(function (error) {
res.status(error.code).send(error.reason);
})
This way I am in control of throwing extra errors from inside the promise-chain. If you want to also handle 'normal' promise errors, you would expand your catch to treat the 'self-thrown' errors separately.
Hope this helps, it is my first stackoverflow answer!
Listen for unhandled errors:
window.addEventListener('unhandledrejection', e => {
// ...
});
window.addEventListener('error', e => {
// ...
});
If error gets swallowed, use self.report(error):
.catch(error => {
self.reportError(error);
});
https://developer.mozilla.org/en-US/docs/Web/API/reportError

Is it any way to throw errors inside promises in node.js?

js Gurus,
I am writing a parser in nodejs (actually iojs) and I have a typical callback pyramid of doom handled with promises. The issue taking 70% of my time is finding errors inside promises.
function parseHomeData(home, web) {
var deferred = Q.defer();
var homepage = web.url(home).then(function () {
// any error here dies silently
parser.getHomeInfo(homepage).then(function (parsedHome) {
console.log(parsedHome);
deferred.resolve(parsedHome);
}, function (err) {
console.log(err);
throw new Error(err);
});
}, function (err) {
console.log(err);
});
return deferred.promise;
};
If I uncomment "any errors here dies silently" it will do. The only way, is to wrap inside try/catch block but even there I can only console.log(error) throw new Error does not work. Is it any way to run iojs with automatic failure option?
Thx
Promises catch exceptions and turn them into rejections. So, if you are inside a .then() handler and you throw an exception, that will generate the same result as returning a rejected promise. This is how promises work.
So, to make your code work, you just need to return the actual promise that web.url() is creating. This will also get rid of some antipatterns you have where you are creating promises unnecessarily.
function parseHomeData(home, web) {
return web.url(home).then(function () {
// any exception error here becomes the rejected promise
return parser.getHomeInfo(homepage).then(function (parsedHome) {
console.log(parsedHome);
return parsedHome;
}, function (err) {
// this handler is only needed if you need the console.log(err) here
console.log(err);
throw new Error(err);
});
}, function (err) {
// this handler is only needed if you need the console.log(err) here
console.log(err);
throw err;
});
};
In fact, without the console.log() statements, the code could all just collapse to this:
function parseHomeData(home, web) {
return web.url(home).then(parser.getHomeInfo.bind(parser));
}
This code block combines several things:
Returns the web.url() promise so if it is rejected for any reason (such as a throw in your .then() handler, that will be returned as a rejected promise from your parseHomeData() function.
No need to create your own Q promise when you already have one you can just return.
Once you implement items 1) and 2) above, then rather than resolve() and reject(), you can just return or throw from within the .then() handler.
You can read more about promise anti-patterns in these references:
Promise Anti-patterns (Bluebird github)
Promise Anti-patterns (Tao of Code)
Promise Patterns & Anti-Patterns

Categories

Resources