Handling Errors (Rejections) in async/await inside Array#map - javascript

Node 8.1.2, I have a structure where one file is calling another file's function in a map. In a real example I would use Promise.all on the map but that's not the question here. Here is the structure:
A.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
return b(thing)
}))
return res.status(200).json(...)
}
B.js:
// Thing -> Promise<Object>
function b (thing) {
return ThingModel.update(...) // this returns a Promise but FAILS and throws an errror
}
module.exports = { b }
OK. So in function b I try to get some async data (from a database). It fails and throws an Uncaught Promise Rejection.
How to make deal with it?
I tried multiple solutions:
A1.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
try {
return b(thing)
} catch (err) {
return next(err)
}
}))
return res.status(200).json(...)
}
But that is still uncaught.
A2.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
try {
things.map(thing => {
return b(thing)
}))
} catch (err) {
return next(err)
}
return res.status(200).json(...)
}
Still unhandled. I tried using Promise.all, I tried double try-catch blocks (since I thought the one inside map might be returning next from the to the map result and not actually from expressStuff function. Still nothing.
The closes I got to the answer was handling the error but then code wouldn't wait for it to be thrown and both res.status() and next would work resulting in race conditions and cannot set headers after they are sent errors.
All I want to do is for the function b to throw an error but catch it in the expressStuff so I can rethrow custom UnprocessableEntityError and pass it to next. It seems like error from file B is not bubbling up to the map where it is called.
How do I do it?
EDIT:
The only way I can make this rejection handled is try-catching it in the B.js. But if I try to rethrow an error/return it - nothing. Error is swallowed. If I try to console.log it - it will be logged though.
DETAILS:
Thanks to marked answer I refactored my actual code and made it to work perfectly.
function expressStuff (res, req, next) {
try {
await Promise.all(things.map(async thing => {
if (ifSomething()) {
await b(thing)
}
}))
} catch (err) {
return next(new MyCustomError('My Custom Error Message'))
}
return res.status(200).json(...)
}

Handling rejections with try/catch works only in async functions when you await the promise - which you haven't attempted yet.
You could do either
async function expressStuff (req, res, next) {
var results;
try {
results = await Promise.all(things.map(b)); // throws when any of the promises reject
} catch (err) {
return next(err) // handle error
}
return res.status(200).json(...)
}
or (like Wait until all ES6 promises complete, even rejected promises)
function expressStuff (req, res, next) {
const resultPromises = things.map(async (thing) => {
try {
return await b(thing); // throws when the promise for this particular thing rejects
} catch (err) {
return defaultValue; // handle error - don't call `next` here
}
});
…
return res.status(200).json(...)
}

Related

How to call an API twice if there is an error occurred?

I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
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)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)

Error handling with deeply nested async functions

I'm having issues catching error when the functions are nested three levels deep. Here is a router with async functions:
router.post('/',
validatevalues,
async (req, res) => {
// if values are invalid then
// return res.status(422).send(errors.msg);
const result = await userController.post(req.body);
return res.status(201).send('Success');
},
);
const userController = {
async post(req) {
try {
await bcrypt.genSalt()
await bcrypt.hash();
await db.query(req.somevalues);
} catch (err) {
console.error(err);
};
};
};
const query = {
return new Promise(function(resolve, reject) {
...
if (err) {
reject(new Error(err));
} else {
resolve(res);
};
};
};
The console.error(err) is printing this stack trace
Error: error: duplicate key value violates unique constraint...
And then I get Uncaught AssertionError at the router level with Mocha testing:
Uncaught AssertionError: expected { Object (_events, _eventsCount, ...) } to have status code 422 but got 201
This seems expected since I am just console.error instead of throwing another newError at the controller level, but what do I need to do? If I throw another error, then wouldn't the stack trace be Error: error: error ...? This doesn't seem right to me.
You should only catch at the highest level:
router.post('/', validatevalues, async (req, res) => {
try {
const result = await userController.post(req.body);
return res.status(201).send('Success');
} catch(error) {
res.status(402).send(error.message);
}
});
If you still want to log at a lower level, you can rethrow the error:
} catch (err) {
console.error(err);
throw err;
} // no semicolon here, its unneccessary

Why is catch() block not running in Objection.js queries and instead then() always runs passing either 0 or 1 as a result?

So when running a query using Objection.js, the query will return data based on success or failure of said query and this data is passed to the then() block as a 0 or 1. Meaning to error handle, I'm having to check falsey values rather than send a response in the catch block. Am I doing something wrong?
const editIndustry = async (req, res, next) => {
const industry = await Industry.query().findById(req.params.industryId);
if (!industry) {
return res.status(404).json({
error: 'NotFoundError',
message: `industry not found`,
});
}
await industry
.$query()
.patch({ ...req.body })
.then(result => console.log(result, 'then() block'))
// never runs
.catch(err => {
console.log(err);
next(err);
});
};
App is listening on port 3000.
1 then() block ran
Your code is working as expected. The reason it's not going into the catch block is because there isn't an error. patch does not return the row. It returns the number of rows changed (see docs).
The function I think you're really looking for is patchAndFetchById (see docs). If you're concerned about generating a 404 error, you can append throwIfNotFound. Obviously, this will throw if it's not found in the database, which will let you catch. You can catch an instance of this error so you can send a proper 404 response. Otherwise, you want to return a 500. You'd need to require NotFoundError from objection.
const { NotFoundError } = require('objection');
const Industry = require('<myIndustryModelLocation>');
const editIndustry = (req, res) => {
try {
return Industry
.query()
.patchAndFetchById(req.params.industryId, { ...req.body })
.throwIfNotFound();
} catch (err) {
if(err instanceof NotFoundError) {
return res.status(404).json({
error: 'NotFoundError',
message: `industry not found`,
});
}
return res.status(500);
}
};

How to handle async callback promise rejection?

Try to handle every exception in my async code (nodeJS, ExpressJS):
Here is almost pseudo code. I use limiter (npm limiter) module with method removeTokens (num, callback(err,remainingRequest)). Big part of code is inside the callback, and I wanna catch and throw any error there to the handler, but for now the error inside callback is still marked as "unhandled exception" and I don't understand why.
app.post('/', async (req, res) => {
try {
...
return getAll();
async function getAll () {
limiter.removeTokens(1, async (err, remainingRequest) => {
try {
throw new Error('THROWN')
} catch (error) {
throw error
}
})
}
} catch (error) {
console.log(error);
}
});
You shouldn't pass async functions into things that don't expect them (unless you catch all errors, as you are with your app.post callback). Instead, give yourself a wrapper for limiter.removeTokens that returns a promise:
function removeTokens(limiter, id) {
return new Promise((resolve, reject) => {
limiter.removeTokens(id, (err, remainingRequest) => {
if (err) {
reject(err);
} else {
resolve(remainingRequest);
}
});
});
}
(You might also look into util.promisify for that.)
Then:
app.post('/', async (req, res) => {
try {
...
await getAll(); // *** Or you might just use `removeTokens(limiter, 1)` directly here
function getAll() {
return removeTokens(limiter, 1);
}
} catch (error) {
console.log(error);
}
});
Here it is using removeTokens directly:
app.post('/', async (req, res) => {
try {
...
await removeTokens(limiter, 1);
} catch (error) {
console.log(error);
}
});
Firstly if possible please share as much code as you can as then it is easy for us to debug where the problem might be.
Coming you your question i think the problem is that in your try..catch block you are throwing the error instead of handling it with a reject. Below i have pasted a code block which you can try and let me know if it works for you. Please not the syntax might be different but the idea is that you have to reject the Promise in case of error.
`````````limiter.removeTokens(1, async (err, remainingRequest) => {
````````````try {
```````````````throw new Error('THROWN')
````````````} catch (error) {
```````````````return Promise.reject(error) //
````````````}
`````````})
``````}
```} catch (error) {
``````console.log(error);
```}
})

Stop Execution of Code After Error thrown in Async Await function

I am creating a Nodejs and express based backend application and trying to handle error in a manner which is suitable for production systems.
I use async await to handle all synchronous operations in the code.
Here is a code snippet of router end points
app.get("/demo",async (req, res, next) => {
await helper().catch(e => return next(e))
console.log("After helper is called")
res.json(1)
})
function helper(){ //helper function that throws an exception
return new Promise((resolve, reject)=> reject(new Error("Demo Error")))
}
After all routes are defined I have added a common error handler that catches exceptions. To simplify it I am adding a simple function
routes.use( (err, req, res, next) => {
console.log("missed all", err)
return res.status(500).json({error:err.name, message: err.message});
});
I expect that the code after await helper() should not execute since the exception has been handled and response sent to frontend. Instead what I get is this error.
After helper is called
(node:46) UnhandledPromiseRejectionWarning: Error
[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the
client
What is the correct way to handle error with async await?
You get After helper is called, because your code continues to execute since it did not return
Don't chain catch with async/await. You do that with Promise.
helper()
.then(data => console.log(data))
.catch(e => console.log(e))
You can handle error like:
app.get("/demo",async (req, res, next) => {
try {
await helper();
// respond sent if all went well
res.json(something)
catch(e) {
// don't need to respond as you're doing that with catch all error handler
next(e)
}
})
you can use try catch to handle the situation
app.get("/demo",async (req, res, next) => {
try {
await helper()
console.log("After helper is called")
res.json(1)
} catch(err) {
next(err)
}
})
function helper(){ //helper function that throws an exception
return new Promise((resolve, reject)=> reject(new Error("Demo Error")))
}

Categories

Resources