Reputation: 2217
What's the correct way to throw an error from an async Mongoose middleware post hook?
The following TypeScript code uses mongoose's post init
event to run some checks that are triggered whenever a function retrieves a doc from mongoDb. The postInit()
function in this example is executing some background checks. It is supposed to fail under certain circumstances and then returns a Promise.reject('Error!');
schema.post('init', function (this: Query<any>, doc: any) {
return instance.postInit(this, doc)
.catch( err => {
return err;
});
});
The hook works fine. I.e. the following code triggers the hook:
MyMongooseModel.findOne({ _id : doc.id}, (err, o : any) => {
console.log(o);
});
However, if postInit()
fails, the error isn't passed back to the calling function. Instead, the document is returned.
I'm looking for the right way to pass this error to the calling function. If the background checks fail, the calling function shouldn't get a document back.
I have tried different ways to raise this error. E.g. throw new Error('Error');
. However, this causes an UnhandledPromiseRejectionWarning
and still returns the document.
Upvotes: 2
Views: 1766
Reputation: 3872
Mongoose maintainer here. Unfortunately, init()
hooks are synchronous and we haven't done a good job documenting that. We opened up a GitHub issue and will add docs on that ASAP. The only way to report an error in post('init')
is to throw
it.
const assert = require('assert');
const mongoose = require('mongoose');
mongoose.set('debug', true);
const GITHUB_ISSUE = `init`;
const connectionString = `mongodb://localhost:27017/${ GITHUB_ISSUE }`;
const { Schema } = mongoose;
run().then(() => console.log('done')).catch(error => console.error(error.stack));
async function run() {
await mongoose.connect(connectionString);
await mongoose.connection.dropDatabase();
const schema = new mongoose.Schema({
name: String
});
schema.post('init', () => { throw new Error('Oops!'); });
const M = mongoose.model('Test', schema);
await M.create({ name: 'foo' });
await M.findOne(); // Throws "Oops!"
}
This is because Mongoose assumes init()
is synchronous internally.
Upvotes: 2
Reputation: 638
In this post init hook method you only receive a doc:
Document.prototype.init()
Parameters doc «Object» document returned by mongo Initializes the document without setters or marking anything modified.
Called internally after a document is returned from mongodb.
Mongoose Documentation: Init HookDocumentation
And for trigger a error you need a done or next method:
Post middleware
post middleware are executed after the hooked method and all of its pre middleware have completed. post middleware do not directly receive flow control, e.g. no next or done callbacks are passed to it. post hooks are a way to register traditional event listeners for these methods.
Mongoose Documentation: Post Middleware
If you only want to know if happened a error in your call, change for this:
MyMongooseModel.findOne({ _id : doc.id}, (err, o : any) => {
if(err) {
throw new Error(err);
}
console.log(o);
});
If you want to propagate the error one option is use a pre hook method:
schema.pre('save', function(next) {
const err = new Error('something went wrong');
// If you call `next()` with an argument, that argument is assumed to be
// an error.
next(err);
});
schema.pre('save', function() {
// You can also return a promise that rejects
return new Promise((resolve, reject) => {
reject(new Error('something went wrong'));
});
});
schema.pre('save', function() {
// You can also throw a synchronous error
throw new Error('something went wrong');
});
schema.pre('save', async function() {
await Promise.resolve();
// You can also throw an error in an `async` function
throw new Error('something went wrong');
});
Example Error Handling: Error Handling
Upvotes: 0