-1

[This question is quite vague, I apologize for it. I'm trying to address my various troubles by answering the question myself]

I am building a Node.js app which has to perform various tasks at given intervals. Here is the global scaffold (involves bluebird promises and mongoose for DB interactions) :

var Promise = require("bluebird");
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');

// Personal modules
var bootApp = require(...);
var doStuffA = require(...);
var doStuffB = require(...);
var doStuffC = require(...);

// running locally, but meant to be deployed at some point
mongoose.connect('mongodb://localhost:27017/myDatabase');
var db = mongoose.connection;

db.on('error', () => {
  console.log("Error : lost connection !"));
  process.exit(1);
});

db.once('open', () => {
 
  bootApp()  // always start by booting
  .then( () => {  // then start the infinite loop of events

    setInterval(doStuffA, 1000*60*60); // 1x/1h
    setInterval(doStuffB, 1000*60*10); // 1x/10min
    setInterval(doStuffC, 1000*60*3); // 1x/3min

  }).catch((e) => {  // errors are handled by doStuffX(), so we should never catch anything here
    console.log(e.message);
    process.exit(1);
  });
});

Each module doStuffX is a function returning a Promise, handling its own errors, and should finish at some point.

Expected behaviour for the entire app :

  • The app should be able to run forever
  • The app should try to doStuffX() at the given interval, regardless of whether it succeeded or failed last time.
  • [Optional :] The app should close smoothly without retrying any doStuff upon receiving a "shut down" signal.

My question : how to build a clean scaffold for such an app ? Can I get rid of setInterval and use promises instead ? One of my main concerns is to make sure the previous instance of doStuffX() is finished before starting the next one, even if it involves "killing" it in some way.

I am open to any link about scaffolding apps, but PLEASE DO NOT GIVE ME AN ANSWER/LINK INVOLVING EXPRESS : I don't need Express, since my app doesn't receive any request. (everything I found so far starts with Express :/)

klonaway
  • 449
  • 3
  • 18
  • 1
    Why would you get rid of `setInterval()`? You need a timer in order to do recurring function calls. There's no replacement for `setInterval()` using promises since the two are pretty much different animals so that whole request just sounds misguided. – jfriend00 Aug 03 '16 at 17:52
  • 1
    What's wrong with the scaffold you have? It looks like it would work just fine. – jfriend00 Aug 03 '16 at 17:53
  • 1
    Are you sure you want to exit the process upon any db error when you expect this to run forever? Shouldn't you be logging the errors and perhaps looking at the the type of errors to decide what action is appropriate? – jfriend00 Aug 03 '16 at 17:54
  • Using `setInterval` feels unsafe to me, since each new call sends a Promise away and I never control its result. But I don't know how to promisify this code... – klonaway Aug 03 '16 at 17:56
  • @jfriend00 : oops yup, gotta reconnect to the db whenever something goes wrong. Working on it. – klonaway Aug 03 '16 at 17:58
  • @jfrien00 : I guess one of my main concerns is to make sure that no instance of `doStuffX` gets stuck at some point despite my careful error handling. I'd like to make sure the previous `doStuffX` is finished before starting the next one, even if I have to "kill" it, but I don't know how to do that. (I'll edit my question) – klonaway Aug 03 '16 at 18:10
  • You already said in your description that `doStuffX()` handles its own errors. If you didn't actually mean that, then please correct your question. There's nothing inherently unsafe about `setInterval()`. If you want to see the result of the promise, you can do `setInterval(function() {doStuffA().then(...).catch(...)}, t)` – jfriend00 Aug 03 '16 at 18:15
  • Please put your actual concerns into your question (use the edit link to edit your question) so we know what you're really asking for help with. – jfriend00 Aug 03 '16 at 18:27

2 Answers2

1

If you don't want to start the next doStuffX() until the previous one is done, then you can replace your setInterval() with repeated setTimeout() calls.

function runA() {
    setTimeout(function() {
        doStuffA().then(runA).catch(function(err) {
            // decide what to do differently if doStuffA has an error
        });
    }, 1000*60*60);
}

runA();

You could also add a timeout to this so that if doStuffA() doesn't respond within a certain amount of time, then you take some other action. This would involve using another timer and a timeout flag.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I just found about doStuffA().timeout(10000).then("whatever"). When I said doStuffX() handles its errors, it is true, but I hadn't yet found a way to stop the process if it runs correctly but takes too much time (I can't explain it all but it may happen...). I think I'm almost there, need to read more about what you made me realize – klonaway Aug 03 '16 at 19:37
  • 1
    @KLôN - `.timeout()` is not an ES6 standard promise features. It is supported by some promise libraries. You would have gotten a much better answer from me if your question actually contained the things you really want to protect against. Asking for a better scaffolding doesn't tell us at all what you want. – jfriend00 Aug 03 '16 at 19:39
  • you are entirely right. The fact is : I asked a very badly formulated question because I couldn't grasp what I really wanted ! Despite what you may think, your comments and answer have helped me a lot. I'm accepting your answer which was helpful. Could you please take some time to read the answer I wrote and tell me if the updated code looks fine ? Thx a lot for your time and help ! – klonaway Aug 03 '16 at 23:15
0

[I answer my own question, trying to put here everything I changed afterwards, in case someone falls into this page someday...]

For the Mongoose part of the scaffold, here is what I got so far for a reliable long-term DB connection :

  • The Mongoose documentation gives a fancy way to ensure the driver will never give up on trying to reconnect with reconnectTries
  • I don't really understand socketOptions and keepalive which seem related to replicas, so I leave them out of my code for now
  • Since Mongoose should autoreconnect whenever something goes wrong, I'll keep the db.once('open') as the access to the app code itself, even though I don't really understand yet the difference with db.on('connected')
  • I recommend reading this.

var Promise = require("bluebird");
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');

// Personal modules
var bootApp = require(...);
var doStuffA = require(...);
var doStuffB = require(...);
var doStuffC = require(...);

// running locally, but meant to be deployed at some point
var uri = 'mongodb://localhost:27017/myDatabase';
// the added option makes sure the app will always try to reconnect...
mongoose.connect(uri, { server: { reconnectTries: Number.MAX_VALUE } });
var db = mongoose.connection;

db.on('error', () => {
  console.log("Error with Mongoose connection."));
});

db.once('open', () => {
 
  bootApp()  // always start by booting
  .then( () => {  // then start the infinite loop of events

    //////////////////////////////////
    /// Here goes the actual stuff ///
    //////////////////////////////////

  }).catch((e) => {  // errors are handled by doStuffX(), so we should never catch anything here
    console.log(e.message);
  });
});

Now, for the actual repetitive stuff, my objective is to make sure everything runs smoothly, and that no process gets stuck. About the changes I made :

  • The methods used are not native ES6 but are specific to bluebird. You can read about .timeout() and .delay() which I find very useful for chaining timeouts and intervals in a clean code.
  • In my mind, .then(runA, runA) should always launch ONE UNIQUE instance of runA but I'm concerned about whether I could actually end up launching two parallel instances...

// Instead of using setInterval in a bluebird promised environment...

setInterval(doStuffA, 1000*60*60); // 1x/1h

// I would have liked a full promise chain, but as jfriend00 stated,
// It will end up crashing because the initial promise is never resolved...

function runA() {
  return doStuffA() 
  .timeout(1000*60*30) // kill the running instance if it takes longer than 30min
  .delay(1000*60*60) // wait 60min
  .then(runA, runA); // whatever the outcome, restart the process
}
runA();

// Therefore, a solution like jfriend00's seems like the way to go :

function runA() {
    setTimeout(function() {
        doStuffA()
        .timeout(1000*60*30)
        .then(runA, runA)
    }, 1000*60*60);
}
runA();
klonaway
  • 449
  • 3
  • 18
  • This isn't a valid answer yet. Answers that contain no actual content themselves other than an external link are not valid here. – jfriend00 Aug 03 '16 at 18:24
  • @jfriend00 : added updated code and stuff. Helps me track the thought process ; hope it might help someone else someday. – klonaway Aug 03 '16 at 23:18
  • 1
    Because you are infinitely chaining your promises, I'm wondering if you will have a memory accumulation issue since the original promise from the first invocation of `runA()` never actually gets resolved as it is continually waiting on a chained result. – jfriend00 Aug 03 '16 at 23:20
  • @jfriend00 : Awww. Bound to stack overflow at some point. Gotta use good old setTimeout() the way you typed it... Thx. – klonaway Aug 03 '16 at 23:27