How to improve my error handling of this chain of promises? - node.js

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)

Related

Unhandled Promise Rejection: AxiosError: Network Error

Working on a MERN stack project. When trying to access the site on localhost on iPhone Posts are posts are not loading. Working fine on adnroid devices.
Screenshot of error
const fetchFeedPosts = async () => {
const URL = `${BASE__URL}api/posts/feed/${LOGGED__IN__USER}`;
await axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((e) => console.log(e.response));
}
fetchFeedPosts()
What the error means
When an Error is thrown (e.g. throw new Error()) it can
be catched locally (e.g. try{...} catch(err) { /** here */ })
or be passed on to the calling function. And in that fashion it bubbles up from caller to caller until it reaches a catch somewhere.
However, if it continues to bubble up, and there's nothing that captures it by the time it reaches the root of your application or code, then that will result in a so-called "Unhandled" error.
Now, as you may know, promises are more like jobs that drift around. They aren't called from the root of your application. But as they bubble up they can also reach a similar root-point, in which case they become "Unhandled Promise rejections".
What to do about it
Unhandled errors or rejections are bad practice though. Errors should be caught somewhere. And without catching them, you can't really know what has caused the error to happen in the first place.
In most cases, you can catch them with a .catch() function, (e.g. yourPromise.catch((err) => {console.err(err)}))
In case your promise is handled in an async function and waited for with an await keyword, then it's slightly different. In that case it makes more sense to use a try-catch block to capture your error.
How to apply it to your code
So, the first way of doing it would be to use the .catch() function
axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((err) => console.error(err));
The alternative is to use the await syntax with a try-catch. If you want to use this syntax, you have to put the async keyword before your function.
try {
const response = await axios.get(URL)
setFeedPosts([...response.data].reverse()))
} catch (err) {
console.log(err);
}
Sure, you could mix the 2, but in most cases that would be rather strange.

How to handle errors using `.catch` in promises

I want to continue code execution when my promises do not have errors, If there are any errors however, I do not want to proceed. How do I gracefully handle this case ?
Eg.
/* start of REST call */
const resultA = fetchThings()
.then(things => returnSomeResult(things))
.then(...)
...
.catch(err => throw err); // cannot proceed computation if there is error
const resultB = fetchOtherThings()
.then(otherTings => returnOtherResult(otherThings))
.then(...)
...
.catch(err => throw err); // cannot proceed if there is error
const resultC = Promise.all([resultA, resultB]) => { // do other stuff }
// return resultC from REST call if successful
When an error occurs in either of the results, I am getting a UnhandledPromiseRejectionWarning on the terminal. It states that 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 a .catch(). I suspect it is because I am throwing the error inside the catch block.
However, I don't want the code to proceed when there is an error from either resultA or resultB.
How can I better handle this scenario of mine ?
My guess would be that you're not handling errors thrown by Promise.all(), which will be rejected if any of the promises it's handling will be rejected.
So you need to catch errors there as well:
Promise.all(…).then(…).catch(e => …)
FWIW, your catch()'s are superfluous:
.catch(err => throw err)
You're catching errors that were thrown and rethrowing them, which isn't necessary (they will be caught by .catch() that you'll attach to Promise.all()).

UnhandledPromiseRejectionWarning Persists Even After Chaining a .catch()

My system consists of an Angular UI and a Node API. The UI submits a file to the API for processing, then gets the result back. This all works - however - the API sometimes fails at processing unexpected data.
I want to be able to catch the error(s) when they arise, stop execution so they won't screw up the UI, then send a message back to UI.
Here is my code so far:
const IncomingForm = require('formidable').IncomingForm;
asynch function myApi(req, res)
{
try // (1)
{
var form = new IncomingForm(...);
form.on('file', async(field, file) =>
{
const [result] = await sometimesBad(inParam); // (2) attach .catch(...);
...
res.send({goodFinal}); // execution should not reach here if error occurs before
});
form.on('end', ()=> {})
form.parse(req)
}
catch (erApi) // (3)
{
... // (4)
}
}
async function sometimesBad(x)
{
try // (5)
{
... lines of code could have run-time error depends on x ...
return goodResult;
}
catch(err2) // (6)
{
... // (7)
}
}
Currently, after hours of searching and trial and error, I:
am able to send a message back by chaining a .catch() at (2)
am unable to stop the execution via any combination of (1), (3), (4), (5), (6), (7), including the use of next(), throw new Error(), await Promise.reject(), return Promise.reject().
am always getting UnhandledPromiseRejectionWarning: Unhandled promise rejection.
Node version: 14.9
Update: In addition to accepted answer, there is no need to have (5), (6), (7).
In your code if (2) throws the error indeed is not handled. To handle it, you need to wrap the code inside async (field, file) => ... into try / catch, similar to how you did on the top level of middleware, and inside the catch you do next(error). Also add default error handler after all routes in your app. See How to return error to the client client without making node server crash? regarding that.
You can stop unhandledRejection(s) from crashing your app by handling them. However, if you fail to handle them using catch blocks, you can also watch for events on the process.
Code Example from the Docs:
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
somePromise.then((res) => {
return reportToUser(JSON.pasre(res)); // Note the typo (`pasre`)
}); // No `.catch()` or `.then()`
Alternatively, you can make your sometimesBad function return a Promise, which would cause all errors happening inside the Promise body to be thrown, which can then be handled in the catch block of the caller.

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.

How to make node throw an error for UnhandledPromiseRejectionWarning

I'm using nock.back to mock out some API calls. When unexpected calls are made, UnhandledPromiseRejectionWarning gets printed to the console, but my tests pass, and those warnings are easily missed in the rest of the console output. I want an exception thrown instead of a silent error. How do I do this?
The way I use promises is:
function myFunction(){
return new Promise((resolve, reject) -> {
try {
// Logic
resolve(logic)
} catch(e) {
reject('Provide your error message here -> ' + e)
}
})
}
Or !
function myFunction().then( // Calls the function defined previously
logic => { // Instead of logic you can write any other suitable variable name for success
console.log('Success case')
},
error => {
console.log('myFunction() returned an error: ' + error)
}
)
UPD
Have you had a look here? https://nodejs.org/api/process.html#process_event_unhandledrejection
It describes an unhandledRejection event being emitted when you have no catch for a rejection from a Promise and provides the code to catch the WARNING and output it nicely to console.
(copy paste)
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason);
// application specific logging, throwing an error, or other logic here
});
Node.js runs on a single process.

Resources