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.
Related
I have callable function in my Cloud Functions:
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
return admin.auth().getUserByEmail(email)
})
And I call it in my Angular application:
public getUserByEmail(email: string) {
return this.cloudFunctions.httpsCallable('getUserByEmail')(email)
}
...
this.getUserByEmail(email)
How should I change my function to be able to use it like this:
this.getUserByEmail(email)
.then(data => console.log(data))
.catch(error => console.log(error))
And return 500 from cloud function on error?
As explained in the documentation, if you want your client (front-end) to receive errors sent from the Callable Cloud Function, you need to "return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
So, in your Cloud Function you need to throw an HttpsError, as shown below. In this example we treat the case of a user-not-found error thrown by the Admin SDK and, in turn we throw an HttpsError with a not-found code.
See other possibilities for the HttpsError codes in the doc (table "Parameter"), and here for the Admin Authentication API errors codes.
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
return admin.auth().getUserByEmail(email)
.catch(error => { //Catching the error thrown by the Admin SDK
if (error.code === 'auth/user-not-found') {
throw new functions.https.HttpsError( //throwing the HttpsError
'not-found',
'User with email (' + email + ') was not found!'
);
} else if (error.code === 'auth/...') {
//Possibly manage other Admin Authentication API errors codes
} else {
//...
}
})
})
And then, in your front-end, you do as follows (again, based on the doc, see the specific section):
this.getUserByEmail(email)
.then(data => console.log(data))
.catch(error => {
var code = error.code;
var message = error.message;
console.log(code + '/' + message);
})
If you have something else to do (that might cause error) before returning the user data in the https callable function, you can do like this:
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
// Return your own promise.
return new Promise((resolve, reject) => {
try {
// Do other stuff there might cause error.
} catch (error) {
// Reject if error.
// But you might need to throw the HttpsError again if the client
// can only get INTERNAL as error message.
return reject(error);
};
return admin.auth().getUserByEmail(email)
.then((data) => {
// Remember to resolve the promise, or else you may get an unexpected
// error, such as timeout or even an irrelevant CORS error.
resolve(data);
}
.catch((error) => {
// Follow #Renaud's idea in his answer.
});
});
});
I'm getting this error when executing the following code:
getAggregatedRatings(query).then(ratings=>{
if(ratings){
return res.json(ratings);
}else{
return res.send(200);
}
}).catch((error)=>{
return res.send(500);
});
function getAggregatedRatings(query){
movieRating.aggregate(query)
.exec((err, ratings)=> {
if (err || !ratings) {
return Promise.reject(err);
}
else {
return Promise.resolve(ratings);
}
});
}
Please note that 'getAggregatedRatings' returns the query results as expected.
Please advise what I'm doing wrong.
Your getAggregatedRatings function is not returning a promise, or anything for that matter, which explains why when trying to access it, the result is undefined.
Try changing it to this:
function getAggregatedRatings(query){
return new Promise((resolve, reject) => {
movieRating.aggregate(query)
.exec((err, ratings)=> {
if (err || !ratings) {
reject(err);
}
else {
resolve(ratings);
}
});
})
}
It appears you may be using mongoose for this query. If you're using a recent version of mongoose, then .exec() already returns a promise so you can avoid the promise anti-pattern of wrapping one promise in a manually created new promise whereas you can instead just use the promise it already returns:
function getAggregatedRatings(query){
return movieRating.aggregate(query).exec().then(result => {
if (!result) {
throw new Error("no results found");
}
});
}
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()
})
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' };
});
}
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');
});