Rethrowing error in promise catch
There is no point to a naked catch and throw as you show. It does not do anything useful except add code and slow execution. So, if you're going to .catch()
and rethrow, there should be something you want to do in the .catch()
, otherwise you should just remove the .catch()
entirely.
The usual point for that general structure is when you want to execute something in the .catch()
such as log the error or clean up some state (like close files), but you want the promise chain to continue as rejected.
promise.then(function(result){
//some code
}).catch(function(error) {
// log and rethrow
console.log(error);
throw error;
});
In a tutorial, it may be there just to show people where they can catch errors or to teach the concept of handling the error, then rethrowing it.Some of the useful reasons for catching and rethrowing are as follows:
- You want to log the error, but keep the promise chain as rejected.
- You want to turn the error into some other error (often for easier error processing at the end of the chain). In this case, you would rethrow a different error.
- You want to do a bunch of processing before the promise chain continues (such as close/free resources) but you want the promise chain to stay rejected.
- You want a spot to place a breakpoint for the debugger at this point in the promise chain if there's a failure.
- You want to handle a specific error or set of errors, but rethrow others so that they propagate back to the caller.
Can't catch error thrown in promise using promises 'catch'
Your comment on my comment suggested the case was different from the other answer. I can't really fit this explanation in a comment, so:
The catch block will not wait for the promise to reject, actually. Try this:
const myPromise = new Promise(() => {
console.log('inside of the promise');
setTimeout(() => { console.log('inside of the timeout function'); throw new Error('ERROR') }, 1000);
}).catch(err => {
console.log('CATCHED ', err);
});
console.log('outside of the catch block, and my promise is', myPromise);
You will find that the outside of the catch message is written to the console prior to the message from inside the timeout function. At that point, the promise is unresolved (pending).When you use setTimeout, the function gets added to the end of JavaScript's queue.
EDIT
You can get around this issue by calling reject on the promise.
const myPromise = new Promise((resolve,reject) => {
setTimeout(() => { reject('ERROR'); }, 1000);
}).catch(err => {
console.log('CAUGHT ', err);
});
This is different because in this case the reject function is kept in a scope bubble (closure), so it is still able to resolve.EDIT 2
I did find another question that has some discussion in the comments about this:
JavaScript Promises - reject vs. throw
From this, I would revise my explanation.
First, it's helpful to keep catch blocks separate in your mind from the catch function you chain onto a promise. Think of the promise as catching a thrown error with a built in invisible catch block. In handling it the promise's catch block will call 'reject' which will trigger the catch function.
Thrown error => promise's invisible (implicit) catch block => reject() => promise's catch() function
It can't catch it, though, if the error is thrown from a different call stack, which it is with setTimeout.
Thrown error (in another call stack from setTimeout) => uncaught exception
The chain stops there, because the exception is bubbling up in a different call stack.
But YOU can still call reject, triggering the catch function, which is what the code snippet above does.
reject => promise's catch() function
It's a shorter chain of events, cutting right to the behavior you want -- the triggering of the code in the catch function.
Throwing inside a catch block in a nested Promise to trigger the catch block of outer Promise, is there an alternative cleaner way?
I think the clean way to do this would be using async/await. But before going there, is your question how to not run the outer promise when the inner promise fails?
The example below:
- When the inner promise rejects, the chain stops.
- When the outer promise rejects, the inner is already fulfilled.
const fun42 = () => { return new Promise((resolve, reject) => { setTimeout(() =>{ resolve(42) reject('something at fun 42 went wrong') }, 500) })}
const fun32 = () => { return new Promise((resolve, reject) => { setTimeout(() =>{ //resolve(32) reject('something at fun 32 went wrong') }, 500) })}
fun32().then(x => { console.log(x) return fun42()}).then( y => { console.log(y)}).catch (e => { console.log(e)})
JavaScript Promises - reject vs. throw
There is no advantage of using one vs the other, but, there is a specific case where throw
won't work. However, those cases can be fixed.
Any time you are inside of a promise callback, you can use throw
. However, if you're in any other asynchronous callback, you must use reject
.
For example, this won't trigger the catch:
new Promise(function() {
setTimeout(function() {
throw 'or nah';
// return Promise.reject('or nah'); also won't work
}, 1000);
}).catch(function(e) {
console.log(e); // doesn't happen
});
Why doesn't the try catch block catch the promise exception?
I can see two possible aspects to your question:
The error being thrown in
test
is in the synchronous (not asynchronous) part oftest
. Why is it a promise rejection rather than a synchronous exception?The promise from
test()
is being rejected, why doesn'tcatch
catch that rejection?
#1 - Why is it a rejection?
Because even if anasync
function throws during the synchronous portion of its work, that just rejects the promise it returns, it doesn't raise a synchronous error. It's just a design decision made during the design of async
functions, and a smart one — if it were a synchronous error when throwing in the synchronous part of the function but a promise rejection after that, it would be chaotic and hard to understand. So it's simple: throwing in an async
function always rejects its promise.(This is consistent with how the promise constructor treats the executor callback you pass into it. When you do new Promise((resolve, reject) => /*...*/})
, the promise constructor calls your function synchronously, but if you throw during the call, it uses what you throw to reject the promise rather than allowing it to continue as a synchronous exception.)
#2 - Why isn't rejection caught by the catch
block?
Because you're not using await
. Promise rejections are only exceptions when you are await
ing the promise. If you use the promise methods then
/catch
/finally
to attach handlers, rejection is handled by calling the rejection handler, not by the exception mechanism.So either use promise methods to attach fulfillment and rejection handlers:
test()
.then(result => {
// ...use `result` here...
})
.catch(error => {
// ...handle/report error here...
});
Or use await
in an async
function (or at the top level of a module if you have top-level await
in your enviroment):// In an `async` function (or the top level of a module in cutting-edge environments)
try {
const result = await test();
// ...use `result` here...
}
catch(err){
// ...handle/report error here...
}
promise catch: how to know if the error comes from promise rejection or from the then statement
then
accepts two parameters¹: A function to call on fulfillment, and another to call on rejection. So you can handle the initial rejection earlier:
fetch(/*...*/)
.then(
// Fulfillment handler
(response) => {
/*...*/
},
// Rejection handler for `fetch` promise
(error) => {
/*...*/
}
)
.catch((error) => {
// This rejection handler only sees errors not handled above
});
The first rejection handler (in the then
call) is only called for rejections of the promise from fetch
, not rejections caused by errors in the fulfillment handler.The second rejection handler (in the catch
call) is called for rejections caused by errors (or rejected promises) from the handlers that come before it (either fulfillment or rejection); it doesn't get called if the original promise is rejected (but it may well get called if the first rejection handler throws an error or returns a promise that is/will be rejected).
So in those cases where you care, you can handle it closer to the source.
Note that all rejection handlers that don't either throw an error or return a promise that is/will be rejected convert rejection (of the original promise) into fulfillment (of the one from then
/catch
). That can matter for downstream then
handlers.
A key thing to understanding this is to remember that
then
(and its wrappers catch
and finally
) return a new promise. That's why:aPromise.then(onFulfilled).catch(onRejected);
is different fromaPromise.then(onFulfilled, onRejected);
The first one hooks up a fulfillment handler to aPromise
, and then hooks up a rejection handler to the promise then
returns (which means the rejection handler will be called if aPromise
rejects or if the fulfillment handler throws an error or returns a promise that rejects). In contrast, the second one only hooks up handlers on aPromise
, so errors/rejections from the fulfillment handler do not trigger the rejection handler.¹ In fact,
.catch(fn)
is just a wrapper for .then(undefined, fn)
. :-) spec link How to handle catch blocks and then of Promise
The key thing here — and one of the key things about using promises — is that then
and catch
create new promises. So the promise that was rejected that wasn't handled was the one created by then
:
let promise = request(false);
promise.then( response => { // Creates new promise, rejection isn't handled
console.log('response' , response);
});
promise.catch( (err) => { // Creates new promise
console.log('got Error', err);
});
This is one of the reasons you see promise chains:request(false)
.then( response => {
console.log('response' , response);
})
.catch( (err) => {
console.log('got Error', err);
});
There, three promises are still created (the original from request
, the one from then
, and the one from catch
), but rejection is handled for all three of them by the final catch
handler.The promise created by then
and catch
work like this:
- If the underlying promise resolves:
- If there's no
then
handler, resolve with the resolution from the original promise - If there's a
then
handler, call it:- If the handler returns a thenable (a promise-like object), hook up to it and resolve or reject based on whether that thenable resolves or rejects
- If the handler returns a non-thenable value, resolve with that value
- If the handler throws an error, reject with that error
- If there's no
- If the underlying promise rejects:
- If there's no
catch
handler, reject with the rejection from the original promise - If there's a
catch
handler, call it and do exactly what's done with thethen
handler above (resolve or reject based on what it returns or throws)
- If there's no
Why a promise reject is not catched within a try catch block but produces an Uncaught error?
Technical reasons behind:
HostPromiseRejectionTracker is an implementation-defined abstracthttps://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker
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.
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.
Related Topics
What's the API Key for in Google Maps API V3
Intercept Paste Event in JavaScript
How to Insert Variables in JavaScript Strings
Differencebetween Decodeuricomponent and Decodeuri
Mongoose, Select a Specific Field with Find
How to Set the Style -Webkit-Transform Dynamically Using JavaScript
How to Sign into Google with Selenium Automation Because of "This Browser or App May Not Be Secure."
In Angular, How to Determine the Active Route
Invoke a Callback at the End of a Transition
How to Load All Files in a Directory Using Webpack Without Require Statements
Why Are Exceptions Used for Rejecting Promises in Js
How to Parseint a String with Leading 0
Why Duck Typing Is Allowed for Classes in Typescript
How to Getting Browser Current Locale Preference Using JavaScript