hurb
hurb

Reputation: 2217

How to throw an error from an async mongoose middleware post hook

What's the correct way to throw an error from an async Mongoose middleware post hook?

Code Example

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.

Expected behavior

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

Answers (2)

vkarpov15
vkarpov15

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

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

Related Questions