This question already has answers here:
Chained promises not passing on rejection
(4 answers)
Closed 2 years ago.
Here is the simplified version of the problem;
there are some promises, few .then() chains, and a .catch() block for error handling;
each promise might resolve or reject hence I use Promise.allSetted to know which promise had been failed based on their order in array and their status; It works fine when all promises resolve but when a promise is rejected, it's status will be shown as "fulfilled" in Promise.allSetteld; If I remove the .catch() block, It will work as expected but we need to keep the .catch() block for logging to the store; So why isn't it just behave as expected? is there any way to make it show "rejected" status with .catch() block?
let a = Promise.resolve("a: Promise.resolved").then(response => response).catch(err=>console.log(err));
let b = Promise.reject("b: Promise.rejected").then(response => response); // no error handling
let e = Promise.reject("e: Promise.rejected").then(response => response).catch(err=>console.log(err));
Promise.allSettled([a,b,e]).then( values => console.log(values) );
You can throw the error from the catch block, so that the error is not handled in the catch:
let a = Promise.resolve("a: Promise.resolved").then(response => response).catch(err => console.log(err));
let b = Promise.reject("b: Promise.rejected").then(response => response); // no error handling
let e = Promise.reject("e: Promise.rejected").then(response => response).catch(err => {
console.log(err);
throw err;
//or Reject the Promise
//return Promise.reject(err)
});
Promise.allSettled([a, b, e]).then(values => console.log(values));
This is also discussed in the Mozilla docs:
p.catch(onRejected);
onRejected
A Function called when the Promise is rejected.
This function has one argument: reason The rejection reason. The Promise
returned by catch() is rejected if onRejected throws an error or
returns a Promise which is itself rejected; otherwise, it is resolved.
Yeah now I understand, we .catch() it!
so by using .catch() (or catch in try{}catch(e){}) we are telling the program: "Don't panic, everything is under control", " I'll handle that". and that make sense now. if we handle it without throwing errors it sounds like everything is fine; so yeah, why not; it should be fulfilled
Related
Today I was just exploring Promises in JavaScript and I came across this:
Promise.reject("Failed");
Gives
Promise { <state>: "rejected", <reason>: "Failed" }
Uncaught (in promise) Failed
Whereas,
Promise.reject("Failed").catch((reason) => console.log(reason));
Gives
Failed
Promise { <state>: "fulfilled", <value>: undefined }
I get the part that in the latter, the rejection is caught and hence just a normal console message but why is the promise itself changed to fulfilled when it was rejected.
A call to .catch will return a pending promise.
Whether that pending promise will fulfill depends on:
The promise on which it is called. It that promise fulfils, then the catch promise will just follow suit and resolve to the same outcome without ever executing the callback that was passed as argument.
If the promise on which it is called is rejected (like in your example), then the callback (passed to catch as argument) is executed. This execution determines how the catch-returned promise (that was pending) is resolved. For instance, if the callback throws an error, that promise will actually get in a rejected state.
Demo:
let p = Promise.reject();
let q = p.catch(() => {
console.log("p rejected");
// Return a promise that will reject 1 second later
return new Promise((resolve, reject) => setTimeout(reject, 1000));
});
let r = q.catch(() => {
console.log("q rejected!");
});
Every Promise.then and Promise.catch returns a fulfilled promise with the value returned from the function handler given to then or catch clause.
Let's take a look at your code.
Promise.reject("Failed").catch((reason) => console.log(reason));
The first Promise.reject("Failed") returns a rejected promise with the value "Failed". The catch clause has an arrow functions. And what is this arrow function returning you may ask? The value returned by console.log which is undefined.
How do you test it you may ask? Simple, paste this in your console.
Promise.reject("Failed").catch((reason) => {
console.log(reason);
return "Whoa, what is this?";
});
function fetchDog(){
fetch("https://dog.ceo/api/breeds/image/fail")
.then(response => response.json())
.then(data => console.log(data))
.catch(function(err) {
console.log('Fetch problem');
});
};
fetchDog();
Using the above example I would like to clarify how .catch here receives the rejected Promise. It's also a good exercise in reading MDN for me.
1 .
Looking at this statement from MDN:
If the Promise that then is called on adopts a state (fulfillment or
rejection) for which then has no handler, a new Promise is created
with no additional handlers, simply adopting the final state of the
original Promise on which then was called.
I translate that to mean, in my example, the .thens return a new promise that is a copy of the promise that .then was called on.
2 .
The Promise.prototype.catch spec also says it behaves the same as calling Promise.prototype.then(undefined, onRejected).
I interpret this to mean, in my example, that the first callback in catch is the onRejected parameter. Therefore, when catch receives a rejected promise, it executes console.log('Fetch problem');.
I also interpret this to mean that when catch invariably receives a fulfilled promise, it returns undefined? (I haven't thought of a way to test this in the console).
3 .
I also read in the .then spec:
If a handler function: doesn't return anything, the promise returned
by then gets resolved with an undefined value.
Therefore, in my code snippet, I interpret this to mean catch returns a promise whose value is undefined.
Based on this understanding, so long as the fetch line returned a fulfilled promise, this fulfilled promise would find its way to catch and catch's callback wouldn't execute. catch would return undefined. (I can't think of a way to test this). I suspect my understanding is wrong.
When catch invariably receives a fulfilled promise, it returns undefined?
No. Calling .catch() will always return a promise. It does that before even knowing whether the promise that it was called on is fulfilled, rejected or still pending.
I interpret this to mean catch returns a promise whose value is undefined.
Yes. You can test this easily with
function handleError(p1) {
const p2 = p1.catch(err => {
console.log('handling problem', err);
});
p2.then(res => {
console.log('final promise fulfilled with', res);
});
}
// handleError(Promise.resolve('success'));
handleError(Promise.reject('error'));
The following two functions behave identically when called.
function func1() {
return asyncFunc()
.then(() => {
// do something
});
}
function func2() {
return asyncFunc()
.then(() => {
// do something
}).catch((err) => {
throw err;
});
}
I know that .catch() is merely syntactic sugar for Promise.prototype.then(undefined, onRejected) per MDN docs. However, I'm confused as to what actually occurs behind the scenes when you omit .catch from a promise chain.
What is actually going on behind the scenes when there is no .catch() in a promise chain? Is a .catch((err) => { throw err;}); being "magically" appended somehow?
The .catch((err) => { throw err;}) would not do anything, it would just re throw the error. So the Promise returned by .catch will be rejected with the error err again:
Promise.reject(new Error('test'))
.catch(err => {
console.error(err)
throw err
})
.catch(err => {
console.error(err)
})
func1 would return a Promise that might be rejected by some event that happens in that chain. So the caller of the func1 might want to handle that error, or if the the caller passes the received Promise further, and does not want to handle that error, then also the caller can omit the catch.
But the one "owning" the chain (the one who received it last and does not pass it to anyone else), is responsible to handle the rejection case.
function func1() {
return asyncFunc()
.then(() => {
// do something
});
}
function callerA() {
return func1()
}
function callerB() {
// callerB does not return the promise retuirned by callerA,
// and does not pass it to any other function, so it has to handle
// the rjection case
callerA().catch(err => {
})
}
On a rejection the js environment will check if there this error is catched anywhere in the chain. If that is not the case then a UnhandledPromiseRejectionWarning might be thrown:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Or in case of the browser the error is logged in the console.
How the js enviroment deals with unhandled rejections depends, nodejs currently emits this warning:
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
And as it says will terminate the application in future versions.
For the browser it is unlikely that it will terminate the tab future. It most likely will always only log that error in the console, but you still should write your code as if the unhandled rejection would terminate the context.
The two examples are definitely functionally equivalent, there is no magic happening behind the scenes. The Promise handler callbacks are designed to automatically handle values that are thrown and apply them to the promise chain.
Consider the following example:
Promise.resolve("{") // broken JSON
.then((json) => {
return JSON.parse(json) // Throws SyntaxError
})
.catch((err) => {
throw err; // Throws the same SyntaxError
})
.catch((err) => {
console.error(err); // Logs the SyntaxError
});
Most Promise implementations have a feature that logs the error when you have not attached an error handler to a promise that was rejected, some even allow you to register a custom event handler (e.g. Chrome and NodeJS) when an unhandled rejection occurs. But not all runtimes are created equal, the Promise implementation in Edge, for instance, does not provide such feature for notifying users of unhandled rejections.
Q: Is a .catch((err) => { throw err;}); being "magically" appended somehow?
A: NO, NOT AT ALL!
The .then method itself returns a promise. And, if you call a .catch method on that returned promise the handler passed in the catch would get called if that promise got rejected. The handler would simply get ignored if the promise got resolved. Read more about error handling in JS promises here.
And, as I already mentioned, the catch method doesn't magically get appended. Your code would just get ignore if the promise returned by .then method would get rejected because you didn't catch it. Run the following code for a better understanding:
var myPromise = new Promise(function(resolve, reject) {
setTimeout(reject, 100);
});
myPromise
.then(() => console.log('hi')); // Prints nothing
myPromise
.then(() => console.log('hi'))
.catch(() => console.log('hello')); // Prints hello
Recently I started using (Async & Await). Before this, I used Promise to make my process Asynchronous. Like:
example.firstAsyncRequest()
.then(firstResponse => {
return example.secondAsyncRequest(firstResponse)
})
.then(secondResponse => {
return example.thirdAsyncRequest(secondResponse)
})
.then(result => {
console.log(result)
})
.catch(err => {
console.log(err)
})
Now I am achieving this like:
try {
const firstResponse = await example.firstAsyncRequest();
const secondResponse = await example.secondAsyncRequest(firstResponse);
const thirdAsyncRequest = await example.thirdAsyncRequest(secondResponse);
console.log(thirdAsyncRequest)
}
catch (error) {
// Handle error
}
In both, the code-block method executes one after another and finally throws an error if any and gets caught by catch block. My question is, is this only a difference of syntax? Please explain or suggest me any link to understand this better.
Thank You
Is there only difference of syntax?
Yes. Your examples are functionally equivalent except that your second example is missing
console.log(thirdAsyncRequest);
...after the third await. (That variable really should be result to match the first code block, or thirdResponse to match the other two response variables).
async/await is syntactic sugar around promise creation and consumption. It's really helpful sugar, but that's all it is. async functions return promises. await expressions let you wait for a promise to settle, either getting the fulfillment value if it's fulfilled or throwing an error if it's rejected. (Since rejections are errors, you can use try/catch to handle them.) An uncaught error in an async function (whether a synchronous error or a promise rejection observed by await) causes the promise the async function returned to be rejected with that error. Otherwise, the function's promise is resolved to whatever the function's code returns: if it returns a non-thenable, the promise is fulfilled with that value; if it returns a thenable, the promise is fulfilled or rejected based on the settlement of that thenable.
(Re "resolve" vs. "fulfill" and such, I've written up this post on promise terminology.)
Once a promise reject() callback is called, a warning message "Uncaught (in promise)" appears in the Chrome console. I can't wrap my head around the reason behind it, nor how to get rid of it.
var p = new Promise((resolve, reject) => {
setTimeout(() => {
var isItFulfilled = false
isItFulfilled ? resolve('!Resolved') : reject('!Rejected')
}, 1000)
})
p.then(result => console.log(result))
p.catch(error => console.log(error))
Warning:
Edit:
I found out that if the onRejected handler is not explicitly provided to the .then(onResolved, onRejected) method, JS will automatically provide an implicit one. It looks like this: (err) => throw err. The auto generated handler will throw in its turn.
Reference:
If IsCallable(onRejected)` is false, then
Let onRejected be "Thrower".
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-performpromisethen
This happens because you do not attach a catch handler to the promise returned by the first then method, which therefore is without handler for when the promise rejects. You do have one for the promise p in the last line, but not for the chained promise, returned by the then method, in the line before it.
As you correctly added in comments below, when a catch handler is not provided (or it's not a function), the default one will throw the error. Within a promise chain this error can be caught down the line with a catch method callback, but if none is there, the JavaScript engine will deal with the error like with any other uncaught error, and apply the default handler in such circumstances, which results in the output you see in the console.
To avoid this, chain the .catch method to the promise returned by the first then, like this:
p.then( result => console.log('Fulfilled'))
.catch( error => console.log(error) );
Even if you use Promises correctly: p.then(p1).catch(p2) you can still get an uncaught exception if your p2 function eventually throws an exception which you intend to catch using a mechanism like window.onerror. The reason is that the stack has already been unwound by the error handling done in the promise. To fix this, make sure that your error code (called by the reject function) does not throw an exception. It should simply return.
It would be nice if the error handling code could detect that the stack has already been unwound (so your error call doesn't have to have a flag for this case), and if anyone knows how to do this easily I will edit this answer to include that explanation.
This code does not cause the "uncaught in promise" exception:
// Called from top level code;
// implicitly returns a Promise
testRejectCatch = async function() {
// Nested within testRejectCatch;
// simply rejects immediately
let testReject = function() {
return new Promise(function(resolve, reject) {
reject('test the reject');
)};
}
//***********************************************
// testRejectCatch entry.
//***********************************************
try {
await testReject(); // implicitly throws reject exception
catch(error) {
// somecode
}
//***********************************************
// top level code
//***********************************************
try{
testRejectCatch() // Promise implicitly returned,
.catch((error) => { // so we can catch
window.alert('Report error: ' + error);
// must not throw error;
});
}
catch(error) {
// some code
}
Explanation:
First, there's a terminology problem. The term "catch" is
used in two ways: in the try-catches, and in the Promises.
So, it's easy to get confused about a "throw"; is it throwing
to a try's catch or to a Promise's catch?
Answer: the reject in testReject is throwing to the Promise's
implicit catch, at await testReject; and then throwing on to
the .catch at testRejectCatch().
In this context, try-catch is irrelevant and ignored;
the throws have nothing to do with them.
The .catch at testRejectCatch satisfies the requirement
that the original throw must be caught somewhere,
so you do not suffer the "uncaught in Promise..." exception.
The takeaway: throws from Promises are throws to .catch,
not to try-catch; and must be dealt-with in some .catch
Edit:
In the above code, the reject propagates up through the .catches.
If you want, you can convert over to propagating up the try-catches.
At line 17, change the code to:
let bad = '';
await testReject().catch((error) => {bad = error});
if (bad) throw bad;
Now, you've switched over to the try-catch.
I ran into this issue, but without setTimeout().
In case anyone else runs into this: if in the Promise constructor you just call reject() synchronously, then it doesn't matter how many .then() and .catch() handlers you add to the returned Promise, they won't prevent an uncaught promise rejection, because the promise rejection would happen before you
I've solved that problem in my project, it's a large enterprise one. My team is too lazy to write empty catch hundreds of times.
Promise.prototype.then = function (onFulfilled, onRejected) {
return baseThen.call(this, (x: any) => {
if (onFulfilled)
onFulfilled(x);
}, (x: any) => {
if (onRejected)
onRejected(x);
});
};