在没有 Express 的情况下正确搭建 Node.js 应用程序(该应用程序不接收请求)

Scaffolding a Node.js app properly without Express (the app doesn't receive requests)

[这个问题比较含糊,请见谅。我正在尝试通过自己回答问题来解决我的各种问题]

我正在构建一个 Node.js 应用程序,它必须按给定的时间间隔执行各种任务。这是全局脚手架(涉及用于 DB 交互的 bluebird promises 和 mongoose):

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);
  });
});

每个模块 doStuffX 都是一个函数,返回一个 Promise,处理自己的错误,并且应该在某个时候完成。

整个应用的预期行为:

我的问题:如何为这样的应用构建干净的脚手架?我可以摆脱 setInterval 并改用 promises 吗?我主要关心的问题之一是确保 doStuffX() 的前一个实例在开始下一个实例之前完成,即使它以某种方式涉及 "killing"。

我对任何 link 脚手架应用持开放态度,但请不要给我 ANSWER/LINK 涉及 EXPRESS :我不需要 Express,因为我的应用没有收到任何请求. (到目前为止,我发现的所有内容都以 Express 开头:/)

[我回答了我自己的问题,试图把我之后更改的所有内容都放在这里,以防有一天有人掉进这个页面...]

对于脚手架的 Mongoose 部分,这是我目前获得的可靠的长期数据库连接:

  • Mongoose documentation 提供了一种奇特的方式来确保驱动程序永远不会放弃尝试与 reconnectTries
  • 重新连接
  • 我不太明白 socketOptionskeepalive 似乎与副本有关,所以我暂时将它们从我的代码中删除
  • 由于 Mongoose 应该在出现问题时自动重新连接,因此我将保留 db.once('open') 作为对应用程序代码本身的访问权限,尽管我还不太了解 db.on('connected') 的区别
  • 我推荐阅读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);
  });
});

现在,对于实际的重复性内容,我的 objective 是确保一切顺利进行,并且没有进程卡住。关于我所做的更改:

  • 使用的方法不是原生 ES6,而是 bluebird 特有的。您可以阅读 .timeout() and .delay(),我发现它对于在干净的代码中链接超时和间隔非常有用。
  • 在我看来,.then(runA, runA) 应该总是启动一个 runA 的唯一实例,但我担心我是否真的可以最终启动两个并行实例...

// 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();

如果您不想在上一个完成之前开始下一个 doStuffX(),那么您可以用重复的 setTimeout() 调用替换您的 setInterval()

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

runA();

您还可以为此添加超时,这样如果 doStuffA() 在一定时间内没有响应,您就可以采取其他措施。这将涉及使用另一个计时器和超时标志。