0

How can I stop a thrown error from propagating all the way down the chain? It shows in my catch() block but it doesn't stop and crashes the server with an uncaught exception.

I am running this as part of a node cron job (node-cron) as:

var cronJob = require('cron').CronJob;
var cron = require('../lib/cron')

var c = new cronJob('* * * * * *', function() {
  console.log('Cron starting');
  mycode.run();
}, function() {
  console.log('Cron executed');
}, true);
 c.start();

In my cron.js

  module.exports = {
    run: function() {
      return job.getAndStore().catch(function(e) {
        // This prints but it keeps on going so to speak - it doesn't 'catch', just notifies me
        console.log('ERROR', e); 
      });
    }
  };

Console dump:

Cron starting
ERROR [TypeError: undefined is not a function]
Cron starting
Uncaught Exception
[TypeError: undefined is not a function]
TypeError: undefined is not a function

I have to do this which I know not quite right:

try {
  run();
} catch(e) { 
  console.log('Now it stops')
}

The run() is part of some cron library that doesn't have any promise support so I am wrapping it in the function to call it.

Edit As I think my issue is related to subsequent calls I believe it has to do with how I handle the Mongo connection on 2+ calls:

    //  Create a Mongo connection
Job.prototype.getDb = function(id) {
  var self = this;
  return new P(function(resolve, reject) {
    if (!self.db) {
      return Mongo.connectAsync(self.options.connection)
      .then(function(c) {
        self.db = c;
        debug('Got new connection');
        resolve(c);
      });
    }
    debug('Got existing connection');
    resolve(self.db);
  });
};

// Fetch stuff
Job.prototype.getAndStore = function(c) {
  return this.getDb().then(function() {
    throw new Error('Boom');
  });
};
cyberwombat
  • 38,105
  • 35
  • 175
  • 251
  • What do you mean by "*it doesn't stop and crashes the server*"? Where exactly does the error get thrown? If your `catch` callback gets called you should be safe. – Bergi Feb 28 '15 at 22:09
  • You have another problem in your code - show us the stack trace. – Benjamin Gruenbaum Feb 28 '15 at 22:09
  • Added more code - It prints out my error in the catch block of the promise but then 'goes on' so to speak resulting in the entire app crashing. I am not sure where my mistake is – cyberwombat Feb 28 '15 at 22:20

2 Answers2

1

Your catch callback is only executed the first time. You are getting the uncaught exception in the second run of the cron job, and it looks like your job.getAndStore() does not return a rejected promise there but throws synchronously. It shouldn't, it should always return a promise.

You can use Bluebirds Promise.try to automatically catch such exceptions and transform them into a promise rejection. Or you wrap your getAndStore function in Promise.method:

var safeGetAndStore = Promise.method(job.getAndStore.bind(job));

module.exports = {
  run: function() {
    return safeGetAndStore().catch(function(e) {
      console.log('ERROR', e); 
    });
  }
};

In your specific case, the problem was that your job did cache the db connection and returned that when it was already available - but you needed to return a promise with a .then method. You should simply cache the promise itself:

Job.prototype.getDb = function(id) {
  if (!this.db) {
    this.db = Mongo.connectAsync(self.options.connection);
  return this.db;
};
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Ok - the safe method does the trick. If I don't do that then only the first cron call gets caught and any subsequent calls doesn't. I had not realized that this cron module went down to the seconds so the stack trace was the result of 2 normal subsequent calls. I wonder why only the first call get caught though though – cyberwombat Feb 28 '15 at 22:52
  • In the first call for some reason it did not throw an exception, but returned a rejected promise. If you showed me your `getAndStore` function, I could tell you more about that :-) – Bergi Feb 28 '15 at 22:56
  • I found my issue - I am creating a mongo connection and saving it but it was not a promise on subsequent calls. I have wrapped the thing in a `P(resolve, reject)` call though perhaps there is a better way to handle this sometimes async situation – cyberwombat Mar 01 '15 at 03:04
  • Yeah, but unfortunately you have used [an antipattern](http://stackoverflow.com/q/23803743/1048572) to solve this. Have a look at my answer update – Bergi Mar 01 '15 at 09:13
  • What I am trying to do in the getDB call is store the connection and not need to use it in the return call in order to do `return this.db.collection('abc')` calls rather than `this.getDb().then(function(db) { return db.collection('abc'); });` – cyberwombat Mar 01 '15 at 13:36
  • No, the point is that you always need to return a promise (even if it is already fulfilled with the stored connection), not sometimes a promise and sometimes a connection - as the code that expects to call `.then()` on it doesn't work (as we've seen). – Bergi Mar 01 '15 at 13:42
  • But it is a promise all the time no? The above you see is the code I fixed to make it work. The original had the issue you are talking about. But I think this is a new question so I add it here http://stackoverflow.com/questions/28794251/how-to-reuse-a-mongo-connection-with-promises – cyberwombat Mar 01 '15 at 13:48
0

Use done, at least if bluebird implements it properly it will work as you expect.

catch(..) is just alias for then(null, ..) which is promise transformer that creates another promise for further processing.

So following should work for you:

  module.exports = {
    run: function() {
      return job.getAndStore().done(null, function(e) {
        // This prints but it keeps on going so to speak - it doesn't 'catch', just notifies me
        console.log('ERROR', e); 
      });
    }
  };
Mariusz Nowak
  • 32,050
  • 5
  • 35
  • 37