Throwing custom error in knex transaction cause api to crash - node.js

I am currently working on api with knex. I have to perform some operations in one big transaction and have to valdiate there also - if any validation returns "false" - transaction must be stoped. Problem is, whenever I toss "my" error there, even though all "Catch"es gets it and res is sended corrently- right after that my whole api crash with error:
Cannot read property "removeListener" of null
which is strange, becouse there are no similar issues with catching errors caused by knex itself.
Strangley, if i would remove throwing of my errors - i will still get unhandled exception
Cannot read property "rollback" of null
in code it looks like this:
f1(){
// (...)
let noErrors = true;
return global.knex
.transaction((trx) => {
return operation1(trx) //Knex operation returning object with parameter "correct"
.then((output)=>{
if(output.correct === false)
throw new Error('CustomError');
})
.then(()=>{ return operation2(trx); })
.then(()=>{ return operation3(trx); })
// (...)
.then(trx.commit)
.catch((error) => {
console.error('TRANS. FAILED');
noErrors = false;
trx.rollback();
throw error; // Both with and without it - failed
});
})
.then(() => {
console.log('TRANS. OK');
})
.then(() => {
if(noErrors)
return {result:'MyResultsHere'};
else
return {result:'ErrorOccured'};
})
.catch((error) => {
return {result:'ErrorOccuredAgain'};
});
}
This function's result (promise) is then returned :
f1().then((output)=>{
console.log(output.result);
// (...) sending response for request here
}
.catch((err) => {
console.error(err);
res.status(500).send();
});
AFter some additiona ltesting - it appears liek i can throw my custom errors, but issue here is with rollback - and soemtiems i get one more error:
TransactionError: Requests can only be made in the LoggedIn state, not
the SentClientRequest state

Looks like you are mixing 2 different transaction handling syntaxes (simple examples below):
knex.transaction(trx => {
// returning promise automatically calls commit / rollback
return operation(1);
})
.then(results => console.log("commit was called automatically", results))
.catch(err => console.log("rollback was called automatically", err))
and
knex.transaction(trx => {
// NOT returning promise so you need to call commit / rollback explicitly
operation(1).then(results => trx.commit(results))
.catch(err => trx.rollback(err));
})
.then(results => console.log("stuff from transaction commit", results))
.catch(err => console.log("error passed to rollback", err))
You are probably trying to do this:
f1(){
// (...)
return global.knex
.transaction(trx => {
return operation1(trx)
.then(output => {
if(output.correct === false) {
// if wrong results promise will reject with "CustomError"
throw new Error('CustomError');
}
})
.then(() => operation2(trx))
.then(() => operation3(trx))
// (...)
;
})
.then(resultsOfLastThen => {
console.log('TRANS. OK', resultsOfLastOperation);
return { result: 'MyResultsHere' };
})
.catch(error => {
return { result: 'ErrorOccured' };
});
}

Related

what is the best way to get data inside a cloud-function in case you don't want to send a response?

I Have the following function and I want to do a conditional inside of the snapshot and then do some actions,
the current issue is I a can see the first console.log in the logs but the the function is not proceeding in to the snapshot for some reason what is the best way to get data inside a cloud-function ? in case you don't want to send them as response ?
export const deleteClient = functions.https.onCall((data, context) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
db.collection('ClientsData')
.doc(data.clientAccountId)
.get()
.then((snapshot) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
admin
.auth()
.deleteUser(data.clientAccountId)
.then(() => {
console.log('Successfully deleted user');
})
.catch((error: any) => {
console.log('Error deleting user:', error);
});
db.collection('ClientsData').doc(data.clientAccountId).delete();
}
})
.catch((err) => {});
});
If you do not want to return anything from the function, you can simply return null. Also you should log any errors in the catch block so you'll know if something is wrong. Try refactoring the code using async-await syntax as shown below:
export const deleteClient = functions.https.onCall(async (data, context) => {
try {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
const snapshot = await db.collection('ClientsData').doc(data.clientAccountId).get()
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
await Promise.all([
admin.auth().deleteUser(data.clientAccountId),
db.collection('ClientsData').doc(data.clientAccountId).delete()
]);
}
} catch (e) {
console.log(e)
}
return null;
});

Capture individual record in catch statement if any error occurs while processing data through Promise.all [duplicate]

This question already has answers here:
Wait until all promises complete even if some rejected
(20 answers)
Closed 3 years ago.
I'm processing multiple records using Promise.all now if any error occurs I want to capture individual record in catch statement
I have written a method callApi now I'm calling callApi method in main code
try {
let result = await callApi(res)
}
catch (err) {
}
async function callApi(records) {
return Promise.all(
records.map(async record => {
await processRecord(record)
})
)
}
I want to display/capture individual record in catch block if any error occurs
ex below
try {
let result = await callApi(res)
}
catch (err) {
console.log('Got error while processing this record', record)
}
But how I will get record variable in catch block
Since it's processRecord that may throw, if you want the record it's processing to be what is caught, rather than the actual error, catch the processRecord error, and throw the record:
function callApi(records) {
return Promise.all(
records.map(record => (
processRecord(record)
.catch((err) => {
throw record;
})
))
)
}
It may be useful to return both the record and the error when catching, though:
try {
let result = await callApi(res)
} catch ({ err, record }) { // <--------
console.log('Got error while processing this record', record);
console.log(err);
}
function callApi(records) {
return Promise.all(
records.map(record => (
processRecord(record)
.catch((err) => {
throw { err, record };
})
))
)
}
Note that because callApi already explicitly returns a Promise, there's no need to make it an async function.
To wait for all requests to finish, and then check the errors on each, do something like:
function callApi(records) {
return Promise.all(
records.map(record => (
processRecord(record)
.then(value => ({ status: 'fulfilled', value }))
.catch(err => ({ status: 'rejected', reason: { record, err } }))
))
);
}
const result = await callApi(res);
const failures = result.filter(({ status }) => status === 'rejected');
failures.forEach(({ reason: { record, err } }) => {
// record failed for reason err
});

Node Promise rejection does not enter catch

I have a method calling findByIdAndRemove on a mongoDB. In case I don't find the id and therefore can't delete it from the DB, I want to throw an error.
Delete dashboard function:
deleteDashboard = (id) => {
return Dashboard.findByIdAndRemove(id).exec((err, dashboard) => {
if (err) return errorHandler.handle('dashboardService', err);
if (dashboard === null) return Promise.reject({ status: 404, message: 'not found' });
return Promise.resolve(dashboard);
});
};
Function call:
return dashboardService.deleteDashboard(id)
.then(dashboard => res.status(200).json(dashboard))
.catch(error => res.status(error.status).json(error));
I don't understand why calling dashboardService.deleteDashboard with an id that isn't in the database, doesn't enter the catch. While debugging, I checked that is enters the if(dashboard === null) condition and thus calls Promise.reject(), but then it enters the then instead of the catch.
I think the problem is here:
return Dashboard.findByIdAndRemove(id).exec((err, dashboard) => {
Usually when you use promise you don't pass a callback. Try to change it to
deleteDashboard = (id) => {
return Dashboard.findByIdAndRemove(id).exec()
.then(dashboard => {
if (dashboard === null) return Promise.reject({ status: 404, message: 'not found' });
return dashboard;
})
.catch(err => {
return errorHandler.handle('dashboardService', err);
})
};
I tried to keep most the code as it is. Just changed the callback for a promise, and the error handle for a catch.

Nested Promise is not propagating error to parent Promise in Node.js?

I'm creating an API using Node.js/TypeScript running Express. Below is an excerpt from my get method. An error is being triggered in the format method, which throws an error that is caught by the promise, but not propagated to the parent promise after a throw:
this.getModel(objectName).findAll(queryParameters).then(function(databaseObjects) {
for (let databaseObject of databaseObjects) {
var jsonObject = {};
//console.log("Database object: ");
//console.log(databaseObject);
transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject);
}).catch((e) => {
console.log("Caught error during format of existing object: ");
console.log(e);
throw e;
});
}
})
.then(() => {
if (metadata) {
this.metadata(objectName, false, transform, res.locals.retval);
delete queryParameters.limit;
delete queryParameters.offset;
console.log("RUNNING METADATA COUNT: ");
this.getModel(objectName).count(queryParameters).then(function(count) {
res.locals.retval.setMetadata("records", count);
return next();
}).catch(function(e) {
this.error(e, res);
return next();
});
} else {
console.log("NO METADATA");
return next();
}
})
.catch((e) => {
// TODO: Move status into error() function
console.log("500 Error on GET");
console.error(e);
res.locals.retval.addError(ErrorCode.InternalError, e);
res.status(ErrorCode.InternalError).send(res.locals.retval);
return next();
});
Here's the output:
(node:8277) Warning: a promise was created in a handler at /Library/WebServer/adstudio/dist/server.js:555:51 but was not returned from it, see
at Function.Promise.bind (/Library/WebServer/adstudio/node_modules/bluebird/js/release/bind.js:65:20)
Caught error during format of existing object:
Test Error
END FUNCTION HAS BEEN REACHED!
Then the request fails to finish.
I've read a lot on Promises and I haven't been able to find an issue/solution similar to mine.
http://bluebirdjs.com/docs/warning-explanations.html
http://taoofcode.net/promise-anti-patterns/
https://www.reddit.com/r/javascript/comments/4bj6sm/am_i_wrong_to_be_annoyed_with_promise_error/
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Chained promises not passing on rejection
http://wiki.commonjs.org/wiki/Promises/A
https://promisesaplus.com/
Running inside that for-loop is not asynchronous, so your promise is resolving basically as soon as the loop finishes, yet before all your formatting finishes.
Use a promise control flow, like bluebird's Promise.each which is serial or just Promise.all. Then any exceptions will be caught.
this.getModel(objectName).findAll(queryParameters).then(function (databaseObjects) {
var promises = databaseObjects.map(databaseObject => {
var jsonObject = {}
// console.log("Database object: ");
// console.log(databaseObject);
return transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject)
}).catch((e) => {
console.log('Caught error during format of existing object: ')
console.log(e)
throw e
})
})
return Promise.all(promises)
})
.catch((e) => {
// TODO: Move status into error() function
console.log('500 Error on GET')
console.error(e)
res.locals.retval.addError(ErrorCode.InternalError, e)
res.status(ErrorCode.InternalError).send(res.locals.retval)
return next()
})

The success block of my promise always executes even when I return a 404

When a user logs in with incorrect email and password, the success block of my client-side promise still executes, even though the server returned a 400.
I'm using Redux with React so I'm calling an action creator which calls an HTTP request using axios. I need help understanding why I'm not handling errors correctly, because the rest of my app's auth functions like signup, logout, etc all behave the same way even though I'm returning 400 statuses from the server.
Here is where I call login from my component, the success block always executes:
handleFormSubmit({
email, password
}) {
this.props.loginUser({
email, password
}).then(() => {
toastr.success("You are logged in!");
}).catch(() => {
toastr.warning("Could not log in");
})
}
Here is the action creator "loginUser", the success block for this function does NOT run when I return a 400 from the server:
export function loginUser({ email, password }) {
return function(dispatch) {
return axios.post(`/users/login`, {
email, password
})
.then(response => {
dispatch({
type: AUTH_USER
});
localStorage.setItem('token', response.headers['x-auth']);
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Incorrect email or password'));
});
}
}
Here is the route '/users/login' Please note that the 400 status does in fact return:
app.post('/users/login', (req, res) => {
var body = _.pick(req.body, ['email', 'password']);
User.findByCredentials(body.email, body.password).then(user => {
return user.generateAuthToken().then(token => {
res.header('x-auth', token).send(user);
});
}).catch(e => {
res.status(400).send();
});
});
You issue is that you're misunderstanding what a catch clause is in promises.
The way you can think if it is just a then with a rejection handler:
.then(null, function(err) {
// handle the error
})
Meaning it only handles the last unhandled error from the promise chain, and you can continue chaining after it no matter what happened.
Example:
new Promise((resolve, reject) => {
setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
console.log(`catch: ${err}`);
return 5;
})
.then((five) => {
// this chains because the error was handled before in the chain
console.log(`catch.then: ${five}`); // 5
})
.catch(() => {
console.log('No error happened between this error handler and previous so this is not logged');
});
To make an error propagate from the current catch to the next error handler, you can return a rejected Promise (or re-throw the error) to make the chain skip all the success handlers until the next fail (or catch) handler.
new Promise((resolve, reject) => {
setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
// return a reject promise to propagate to the next error handler
return Promise.reject(err);
// can also `throw err;`
})
.then((nothing) => {
// this doesn't happen now
console.log(nothing);
})
.catch(console.error); // this logs the error
Side note: When you don't provide a rejection handler in a Promise chain (the second parameter in .then), the default rejection handler essentially behaves like:
function defaultRejectionHandler(err) {
throw err;
}
Which means it re-throws whatever error was passed into it so the error can propagate to the next error handler that you do specify.
The problem is in your catch handler of loginUser function.
If you want to catch the error farther down the promise chain, you'll need to throw the error in the catch block.
.catch(() => {
dispatch(authError('Incorrect email or password'));
throw Error('Incorrect email or password');
});

Resources