Are there reasonable exemptions for eslint promise/no-nesting rule? - eslint

An eslint promise/no-nesting rule violation will occur for the following example:
return foo()
.catch(handleErrorAndReject('foo'))
.then(() => bar().catch(handleErrorAndReject('bar')))
.then(() => baz().catch(handleErrorAndReject('baz')))
Note that handleErrorAndReject is a curried function like this:
const handleErrorAndReject = action => error => {
console.warn(`${action} fails with error`, error)
res.status(400).send(error.message).end()
return Promise.reject(error)
}
But the nested catches seem necessary in this case, because they vary by stage. Is this a case where I should just eslint-disable, or is there a better way to structure this code?

Related

Best way to handle errors in NodeJS

I started working with NodeJS a couple weeks ago. I am wondering what is the best way to handle errors in NodeJS?
Right now, I am doing it in all my controllers methods. For example:
exports.myMethod = async (req, res, next) => {
try {
// My method operations here
} catch(err) {
const email = new Email(); // This is a class that I create to notify me when an error happens. errorEmail(mailBody, mailSubject)
await email.errorEmail(err, "Email Subject - Error");
}
}
Is it a good way? I mean, is there a better/more efficient way to handle errors in NodeJS?
Thanks
Error handling when using Promises (or async/await) is pretty straight forward. You don't want to have lots of duplicates of error handling code and extra try/catch blocks all over the place.
The way I find best is to put the error handling at the highest possible level (not deep in the code). If an exception is thrown, or a Promise rejects, then the failure will percolate up to the point where you catch it and handle it. Everything in between doesn't have to do that if it's handled once at the appropriate place.
So your code can start looking cleaner like this:
// module #1
exports.myMethod = async () => {
// My method operations here
return result;
}
// module #2
exports.anotherMethod = async () => {
const result = await module1.myMethod();
// do more stuff
return anotherResult;
}
// module #3
exports.topMethod = () => {
module2.anotherMethod()
.then((res) => {
console.log("all done", res);
})
.catch((err) => {
const email = new Email(); // This is a class that I create to notify me when an error happens. errorEmail(mailBody, mailSubject)
email.errorEmail(err, "Email Subject - Error")
.then(() => console.log("done, but errors!", err);
});
}
The nice thing here is that the only place I have to add extra error handling is way at the top. If anything deep in the code fails (and it can get much more deep) then it will just return up the chain naturally.
You are free to put .catch statements anywhere in between for doing retries, or to handle expected errors quietly if you want, but I find using .catch is cleaner that wrapping sections of code with try/catch blocks.

How to improve my error handling of this chain of promises?

I am new to TypeScript/JavaScript and Node.js and coming from Java/Scala background.
I am writing a simple script in TypeScript to collect some data and send them as an HTTP POST request to a server using axios.
makePromiseToGetA()
.then((a: A) => makePromiseToGetB(a))
.then((b: B) => sendHttpRequestAxios(b))
.then((resp: AxiosResponse) => outputResponse(resp))
.catch((err: Error) => handleError(err))
Now I want to improve the error handling. Specifically, I want to handle AxiosError using my function handleAxiosError(ae: AxiosError) in addition to the generic error handling (function handleError)
Now I see two options to do that:
Modify the handleError function like this:
// pseudocode because I don't know how to code this in TypeScript
function handleError(err: Error): void {
if (err instanceof AxiosError) {
handleAxiosError(err as AxiosError);
}
... // handle Error
}
"catch" AxiosError after sendHttpRequestAxios, handle the error, and then re-throw it:
makePromiseToGetA()
.then((a: A) => makePromiseToGetB(a))
.then((b: B) => sendHttpRequestAxios(b).catch((ae: AxiosError) => {handleAxiosError(ae); throw ae;}))
.then((resp: AxiosResponse) => outputResponse(resp))
.catch((err: Error) => handleError(err))
How would you suggest handle AxiosError with handleAxiosError in addition to generic error handling using handleError ?
Your #1 seems like a reasonable solution to me if you generally want to handle AxiosError instances differently from other errors. The problem with #2 is that you'll end up handling the error twice: Once in the Axios-specific rejection handler and then again in handleError.
If you don't like that instanceof approach (in handleError or in the rejection handler at the end), you can use nesting:
makePromiseToGetA()
.then((a: A) => makePromiseToGetB(a))
.then((b: B) =>
sendHttpRequestAxios(b)
.then((resp: AxiosResponse) => outputResponse(resp))
.catch((err: AxiosError) => handleAxiosError(ae))
)
.catch((err: Error) => handleError(err))
That takes advantage of the fact that the Axios part is the last non-rejection part of the chain. So you handle it via handleAxiosError, converting rejection to fulfillment — but nothing uses the resulting fulfillment, so you're good. If some other error occurs, though, you end up in the final rejection handler.
Side note: It's just an example and your real code is probably more complex (though perhaps the rejection handlers aren't), but when passing a fulfillment value or rejection reason to a function as its argument, there's no need for a wrapper arrow function:
makePromiseToGetA()
.then(makePromiseToGetB)
.then((b: B) =>
sendHttpRequestAxios(b)
.then(outputResponse)
.catch(handleAxiosError)
)
.catch(handleError)

Parameterize Jest's expect

I can't pass a function that throws into expect() as due to operator precedence that will throw before Jest can try-catch it.
So instead of this:
expect(thisThrows())
We must do this:
expect(() => thisThrows())
But say I want to parameterize it:
test("foo", () => {
const sut = (arg) => { if (!arg) throw new Error(); };
expect(sut(undefined)).toThrow();
expect(sut(null)).toThrow();
expect(sut(0)).toThrow();
expect(sut("")).toThrow();
expect(sut(10)).not.toThrow();
});
This still has the original problem.
How can I do something like this neatly, so I can keep my tests DRY?
Since sut() throws an error, it cannot be called directly as expect(sut(undefined) because the error is thrown immediately and it cannot be asserted.
It should be treated the same way as with expect(() => thisThrows()):
expect(() => sut(undefined)).toThrow();

NodeJS: Promise won't catch thrown exceptions

I have a code like this (simplified):
getStreamFor(path) {
// both, remote and local, return a Promise
if(...) { return getRemoteFileStream(path); }
else { return getLocalFileStream(path); }
}
getRemoteFileStream(path) {
// should throw in my case (MyError)
const validPath = validatePath(path);
return readStreamIfValid(validPath);
}
and in the test case:
it('should throw MyError', () => {
return getStreamFor(path)
.then(() => {})
.catch(error => expect(error).to.be.instanceOf(MyError));
});
The problem is, that when the validatePath(path) Method throws (due to invalid path), nothing get caught in the test case promise. The output in my terminal / console is a regular exception as if it was uncaught.
Does anybody have an idea, why the the Promise wouldn't recognize the throw? How can I fix it without probably surrounding the call in the test case with another "try catch" (since the promise should do that for me)?
Maybe there is a general best practise how to structure Promises in order to avoid error swallowings like these?
Thanks for your help!
The problem here is that validatePath() is not part of the promise chain returned by getRemoteFileStream()
One possible solution is the following:
getRemoteFileStream(path) {
return Promise.resolve()
.then(() => validatePath(path))
.then(validPath => readStreamIfValid(validPath));
}
An exception thrown by validatePath() would now be handled in the Promise's catch handler.

Node promises: use first/any result (Q library)

I understand using the Q library it's easy to wait on a number of promises to complete, and then work with the list of values corresponding to those promise results:
Q.all([
promise1,
promise2,
.
.
.
promiseN,
]).then(results) {
// results is a list of all the values from 1 to n
});
What happens, however, if I am only interested in the single, fastest-to-complete result? To give a use case: Say I am interested in examining a big list of files, and I'm content as soon as I find ANY file which contains the word "zimbabwe".
I can do it like this:
Q.all(fileNames.map(function(fileName) {
return readFilePromise(fileName).then(function(fileContents) {
return fileContents.contains('zimbabwe') ? fileContents : null;
}));
})).then(function(results) {
var zimbabweFile = results.filter(function(r) { return r !== null; })[0];
});
But I need to finish processing every file even if I've already found "zimbabwe". If I have a 2kb file containing "zimbabwe", and a 30tb file not containing "zimbabwe" (and suppose I'm reading files asynchronously) - that's dumb!
What I want to be able to do is get a value the moment any promise is satisfied:
Q.any(fileNames.map(function(fileName) {
return readFilePromise(fileName).then(function(fileContents) {
if (fileContents.contains('zimbabwe')) return fileContents;
/*
Indicate failure
-Return "null" or "undefined"?
-Throw error?
*/
}));
})).then(function(result) {
// Only one result!
var zimbabweFile = result;
}).fail(function() { /* no "zimbabwe" found */ });
With this approach I won't be waiting on my 30tb file if "zimbabwe" is discovered in my 2kb file early on.
But there is no such thing as Q.any!
My question: How do I get this behaviour?
Important note: This should return without errors even if an error occurs in one of the inner promises.
Note: I know I could hack Q.all by throwing an error when I find the 1st valid value, but I'd prefer to avoid this.
Note: I know that Q.any-like behavior could be incorrect, or inappropriate in many cases. Please trust that I have a valid use-case!
You are mixing two separate issues: racing, and cancelling.
Racing is easy, either using Promise.race, or the equivalent in your favorite promise library. If you prefer, you could write it yourself in about two lines:
function race(promises) {
return new Promise((resolve, reject) =>
promises.forEach(promise => promise.then(resolve, reject)));
}
That will reject if any promise rejects. If instead you want to skip rejects, and only reject if all promises reject, then
function race(promises) {
let rejected = 0;
return new Promise((resolve, reject) =>
promises.forEach(promise => promise.then(resolve,
() => { if (++rejected === promises.length) reject(); }
);
}
Or, you could use the promise inversion trick with Promise.all, which I won't go into here.
Your real problem is different--you apparently want to "cancel" the other promises when some other one resolves. For that, you will need additional, specialized machinery. The object that represents each segment of processing will need some way to ask it to terminate. Here's some pseudo-code:
class Processor {
promise() { ... }
terminate() { ... }
}
Now you can write your version of race as
function race(processors) {
let rejected = 0;
return new Promise((resolve, reject) =>
processors.forEach(processor => processor.promise().then(
() => {
resolve();
processors.forEach(processor => processor.terminate());
},
() => { if (++rejected === processors.length) reject(); }
);
);
}
There are various proposals to handle promise cancellation which might make this easier when they are implemented in a few years.

Resources