What are the consequences of handling a rejected promise too early? - javascript

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Handling a rejected promise too early has consequences further down the promise chain.
What kind of consequences can be there? In which cases should early handling of rejections be avoided and in which cases should early handling of rejections be preferred?

The critical part to understand is that catch() also returns a promise.
Consider the following example. Since the intermediate catch has no return BUT catch() itself returns a promise, the final catch never fires and the final then() does ... receiving a result of undefined from the prior catch() promise
const func = (val) => {
return new Promise((resolve,reject)=> {
// not really asynchronous, only contrived for demo
val === 1 ? resolve(val) : reject('Oooops')
}).catch(err=> {console.log('Err in func catch:', err)})
}
func(200).then(res=> console.log('Then res=', res))// receives undefined from prior catch
.catch(err=> console.log('Never gets called'))
So you could do several things differently here depending on preferred logic.
You could return a different expected value from the intermediate catch and look for that in the final then, or return another rejected promise or thrown error to be caught in the final catch().
Or...don't use the middle catch at all if it serves no value intercepting it.
Example use case for using the intermediate catch would be:
A map() of request urls passed through a function that returns the request promise back to your map array but you are OK if not all succeed.
Without the intermediate catch intercepting, the whole chain would break at first bad request. Instead you can return something from intermediate catch that you filter out in a Promise.all().then()

Related

Are `.then` and `.catch` Promise handlers somehow paired and treated like `.then(resolutionHandler, rejectionHandler)`?

When working with promises in JavaScript, we have a possibility to use .then, .catch and .finally. Each of these methods returns a new Promise object.
Using .then is pretty straightforward - we chain them. The use case for finally - is to put it at the end of the chain of .then and .catch. But
In the code below, as I understand, we initialize promise p which can resolve or reject. I could use .then(resolutionHandler, rejectionHandler), which would be self-explanatory as it's only 1 .then "handler" with both handlers, but in case of sequencing .then and .catch instead of the latter approach-
**Are .then and .catch handlers somehow paired and treated like .then(resolutionHandler, rejectionHandler)? or something else is happening? **
const p = new Promise((resolve, reject) => {
reject("ups...");
});
p
.then(data => {
// this is success handler for Promise "p"
})
.catch(err => {
// Is this failure handler for Promise "p"?
})
Not exactly. When you have p.then(handleThen).catch(handleCatch), if p rejects, handleCatch will handle it. But handleCatch will also handle errors thrown by handleThen.
Although those sorts of errors are pretty unusual with if handleThen contains only synchronous code, if handleThen returns a Promise, handleCatch will be able to handle that Promise if it rejects.
<somePromiseChain>
.catch(handleCatch);
will have the handleCatch handle any errors thrown anywhere in the above Promise chain.
In contrast, with p.then(resolutionHandler, rejectionHandler), the rejectionHandler will only handle a rejection of p. It will completely ignore anything that occurs in resolutionHandler.
If the resolutionHandler is completely synchronous, doesn't return a Promise, and never throws (which is pretty common), then .then(resolutionHandler, rejectionHandler) is indeed equivalent to.then(resolutionHandler).catch(rejectionHandler). But it's usually a good idea to use .then(..).catch(..).
With p.then(success).catch(fail), the .catch is actually attached to the Promise returned by the .then - but if p rejects, the .then's Promise rejects as well, with the same value as p's rejection. It's not that the catch is attached directly to p, but it's attached to a .then which passes p's rejection through.
Every .then / .catch is attached directly to the upper Promise it's called on, but sometimes that upper Promise passes through the value from its upper Promise unaltered. That is. p.then(undefined, fail).then(success) will run success if p resolves, with the intermediate .then passing the resolution through. With rejection, p.then(success).catch(fail) will run fail because the .then(success) passes the failure from p through unaltered.
Calling catch internally calls reject
As per mdn
The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise.prototype.then(undefined, onRejected) (in fact, calling obj.catch(onRejected) internally calls obj.then(undefined, onRejected)).

Unhandled Rejection Error - why? // Promise Chain Design decision?

A) See please the code below.
If I use line // 1 I get the onError method called, that's OK.
But if I use line // 2 (and comment out // 1), then onError is still called but there is also an unhandled rejection error. Why? This kind of forces me to do chaining as in // 1. If I don't do chaining but call catch on the original promise then I get the unhandled rejection error.
B) Why the calls p.then and p.catch do not return the original promise p? I find this weird. In all libs which I have seen to allow chaining, the original object (i.e. the this object) is returned. In JavaScript when we do promise1.then(...) a new promise2 is a returned which is not the promise1. I find this weird. What is the logical reasoning behind this? To me it seems like an unnecessary complication and even a bad practice. Yet another gotcha which one needs to remember when working with JavaScript. But OK... I am sure smart people decided to take this approach so... what is the reasoning behind this decision? Any ideas?
function onSuccess () {
console.log('Success occurred!')
}
function onError () {
console.log('Error occurred!')
}
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, 2000)
})
var p1 = promise.then(onSuccess);
var p2 = p1.catch(onError); // 1 //
// var p2 = promise.catch(onError); // 2 //
console.log("DONE!");
To answer your first question, this is quite simple:
You have 2 promise object when you get to those lines you mentioned: one is held by promise variable, and the other by p1 variable. When you chain the catch directly to promise, it does not apply to the promise that is coming back from the then chaining - which is stored in p1- so you get unhandled promise rejection error on the promise that comes back from the then clause.
But, when you chain it to p1, which is already chained to promise, then the catch clause covers them both so no error here.
The difference essentailly is that line //1 can catch rejections / errors both from the original promise and then .then promise, but line //2 catches only rejects from original promise, leaving rejectes from the .then unhandled.
About 2nd question I'm sorry, but too high level for me and I don't wanna give incomplete / inaccurate answer.

How to handle async errors correctly?

When making a GraphQL query, and the query fails, Apollo solves this by having a data-object and an error-object.
When an async error is happening, we get the same functionality with one data-object and one error-object. But, this time we get an UnhandledPromiseRejectionWarning too, with information about: 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..
So, we obviously need to solve this, but we want our async-functions to cast errors all the way up to Apollo. Do we need to try...catch all functions and just pass our error further up the tree? Coming from C#, were an exception just goes all the way to the top if never caught, it sounds like a tedious job to tell Apollo GraphQL that one (or more) leaves failed to retrieve data from the database.
Is there a better way to solve this, or is there any way to tell javascript/node that an uncaught error should be passed further up the call tree, until it's caught?
If you correctly chain your promises, you should never see this warning and all of your errors will be caught by GraphQL. Assume we have these two functions that return a Promise, the latter of which always rejects:
async function doSomething() {
return
}
async function alwaysReject() {
return Promise.reject(new Error('Oh no!'))
}
First, some correct examples:
someField: async () => {
await alwaysReject()
await doSomething()
},
// Or without async/await syntax
someField: () => {
return alwaysReject()
.then(() => {
return doSomething()
})
// or...
return alwaysReject().then(doSomething)
},
In all of these cases, you'll see the error inside the errors array and no warning in your console. We could reverse the order of the functions (calling doSomething first) and this would still be the case.
Now, let's break our code:
someField: async () => {
alwaysReject()
await doSomething()
},
someField: () => {
alwaysReject() // <-- Note the missing return
.then(() => {
return doSomething()
})
},
In these examples, we're firing off the function, but we're not awaiting the returned Promise. That means execution of our resolver continues. If the unawaited Promise resolves, there's nothing we can do with its result -- if it rejects, there's nothing we can do about the error (it's unhandled, as the warning indicates).
In general, you should always ensure your Promises are chained correctly as shown above. This is significantly easier to do with async/await syntax, since it's exceptionally easy to miss a return without it.
What about side effects?
There may be functions that return a Promise that you want to run, but don't want to pause your resolver's execution for. Whether the Promise resolves or returns is irrelevant to what your resolver returns, you just need it to run. In these cases, we just need a catch to handle the promise being rejected:
someField: async () => {
alwaysReject()
.catch((error) => {
// Do something with the error
})
await doSomething()
},
Here, we call alwaysReject and execution continues onto doSomething. If alwaysReject eventually rejects, the error will be caught and no warning will be shown in the console.
Note: These "side effects" are not awaited, meaning GraphQL execution will continue and could very well finish while they are still running. There's no way to include errors from side effects inside your GraphQL response (i.e. the errors array), at best you can just log them. If you want a particular Promise's rejection reason to show up in the response, you need to await it inside your resolver instead of treating it like a side effect.
A final word on try/catch and catch
When dealing with Promises, we often see errors caught after our function call, for example:
try {
await doSomething()
} catch (error) {
// handle error
}
return doSomething.catch((error) => {
//handle error
})
This is important inside a synchronous context (for example, when building a REST api with express). Failing to catch rejected promises will result in the familiar UnhandledPromiseRejectionWarning. However, because GraphQL's execution layer effectively functions as one giant try/catch, it's not really necessary to catch your errors as long as your Promises are chained/awaited properly. This is true unless A) you're dealing with side effects as already illustrated, or B) you want to prevent the error from bubbling up:
try {
// execution halts because we await
await alwaysReject()
catch (error) {
// error is caught, so execution will continue (unless I throw the error)
// because the resolver itself doesn't reject, the error won't be bubbled up
}
await doSomething()

How can I resolve a Promise early in the chain?

I'm doing some HTTP calls in Node.js and want to examine whether or not the request failed or not - by this I mean that an error is not necessarily considered a "failure condition", but that I want to execute some business logic based on that. I have something similar to the following code (though obviously this is contrived since I simplified it):
let p = new Promise(function(resolve, reject) {
// In the real implementation this would make an HTTP request.
// The Promise resolution is either a response object or an Error passed to the `error` event.
// The error doesn't reject because the value I'm actually interested in getting is not the response, but whether the HTTP call succeeded or not.
Math.random() <= 0.5 ? resolve({ statusCode: 200 }) : resolve(new Error());
});
p.then(ret => { if (ret instanceof Error) return false; }) // This line should resolve the promise
.then(/* Handle HTTP call success */);
Basically I want to say, "if I resolved to an error object, just bail out and return false. Otherwise assert some more stuff on the response object and maybe return true, maybe return false."
How can I resolve the promise early and not execute the rest of the chain? Am I thinking about this all wrong? I'm not rejecting the promise if the HTTP call errors because AFAICT you can't get a value out of .catch() (this promise eventually gets passed to Promise.all) in the same way you can with .then(), but I could be wrong.
I'm on Bluebird, FWIW, so feel free to use extra stuff from them.
You can get values out of a catch(), just return them, as stated on the docs:
By not returning a rejected value or throwing from a catch, you "recover from failure" and continue the chain
That would be the best implementation ;)
Just don't use a chain here, but only a single handler:
p.then(ret => {
if (ret instanceof Error) return false; // This line will resolve the promise
/* else handle HTTP call success, and return true/false or another promise for it */
});

How do you prevent throwing exceptions in functions that return promises?

Functions that return promises should not throw exceptions but instead reject the promise, but what's the best practice for ensuring that this will always be the case? For example, even if I have an extremely simple class method like this...
foo(x) {
return this.promiseReturningApi.someMethod('bar', x);
}
...exceptions could be thrown if this.promiseReturningApi is undefined or someMethod is not a function (unless it's called after the first function in a promise chain).
To ensure that an exception is never thrown, is it necessary to always use a try-catch block like this:
foo(x) {
try {
return this.promiseReturningApi.someMethod('bar', x);
} catch (e) {
return Promise.reject(e);
}
}
(Or use something like Promise.resolve().then(() => this.promiseReturningApi.someMethod('bar', x)))
for any function that will return a promise, or how else would one ensure that exceptions are not thrown?
At the risk of committing the `explicit promise constructor anti-pattern, this would work:
foo(x) {
return new Promise(resolve => {
resolve(this.promiseReturningApi.someMethod('bar', x));
});
}
In the absence of any error being thrown, this will return the promise yielded by promiseReturningAPi as is. If an uncaught error is thrown anywhere, be it in the this.promiseReturningApi.someMethod line for whatever reason, or possibly even further down inside the call chain (such as inside promiseReturningApi, depending on how it is written), then the promise returned by foo will be rejected for that error reason. This is due to the specification of how thrown errors inside the "executor" (the function passed to new Promise) are handled--namely, they result in a rejected promise.
However, you should think long and hard about whether you really want to turn what is in essence an error in program logic (for example, a missing someMethod) into a rejected promise. You may find it is a better structure for your program to let "real" errors be errors, and bubble up and be reported as errors are, and reserve rejected promises for failed asynchronous operations such as a network request failing.
Here's what I think is the best practise
/**in provider**/
foo(x) {
return this.api.method(x); //Promise
}
/**in component**/
this.provider.foo('cat')
.then((res) => { console.log(res) }) //resolved
.catch((err) => { console.log(err) }) //rejected
In the function that returns the promise you could use promise.catch(...) like mentioned here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch instead of the try/catch wrapper. This would potentially allow you to just return this.promiseReturningApi.someMethod('bar', x);.

Categories