【问题标题】:Scaffolding a Node.js app properly without Express (the app doesn't receive requests)在没有 Express 的情况下正确搭建 Node.js 应用程序(应用程序不接收请求)
【发布时间】:2016-12-09 13:58:57
【问题描述】:

[这个问题很模糊,对此我深表歉意。我正在尝试通过自己回答问题来解决我的各种问题]

我正在构建一个 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 的函数,处理自己的错误,并且应该在某个时候完成。

整个应用程序的预期行为:

  • 应用应该能够永远运行
  • 应用应在给定的时间间隔内尝试doStuffX(),无论上次是成功还是失败。
  • [可选:] 收到“关闭”信号后,应用应顺利关闭,无需重试任何 doStuff

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

我对任何关于脚手架应用程序的链接持开放态度,但请不要给我一个涉及快递的答案/链接:我不需要快递,因为我的应用程序没有收到任何请求。 (到目前为止,我发现的所有内容都以 Express 开头:/)

【问题讨论】:

  • 你为什么要摆脱setInterval()?您需要一个计时器来执行重复的函数调用。 setInterval() 使用 Promise 是无法替代的,因为两者是完全不同的动物,所以整个请求听起来都是错误的。
  • 你的脚手架有什么问题?看起来它会工作得很好。
  • 当您希望它永远运行时,您确定要在任何数据库错误时退出该进程吗?您不应该记录错误并查看错误类型来决定合适的操作吗?
  • 使用setInterval 对我来说感觉不安全,因为每个新调用都会发送一个 Promise,而我从不控制它的结果。但我不知道如何承诺这段代码......
  • @jfriend00 :哎呀,是的,每当出现问题时都必须重新连接到数据库。正在努力。

标签: node.js mongoose promise bluebird scaffolding


【解决方案1】:

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

对于脚手架的 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);
  });
});

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

  • 使用的方法不是原生 ES6,而是特定于 bluebird。您可以阅读有关 .timeout().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();

【讨论】:

  • 这不是一个有效的答案。除了外部链接之外,不包含任何实际内容的答案在这里无效。
  • @jfriend00 :添加了更新的代码和内容。帮助我跟踪思考过程;希望有一天它可以帮助别人。
  • 因为你无限地链接你的承诺,我想知道你是否会遇到内存积累问题,因为第一次调用 runA() 的原始承诺从未真正得到解决,因为它一直在等待一个链接的结果。
  • @jfriend00:Awww。在某些时候绑定到堆栈溢出。必须按照您输入的方式使用旧的 setTimeout() ......谢谢。
【解决方案2】:

如果您不想在上一个完成之前开始下一个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() 在一定时间内没有响应,那么您可以采取其他措施。这将涉及使用另一个计时器和超时标志。

【讨论】:

  • 我刚刚发现了 doStuffA().timeout(10000).then("whatever")。当我说 doStuffX() 处理它的错误时,这是​​真的,但我还没有找到一种方法来停止该过程,如果它运行正确但需要太多时间(我无法解释这一切,但它可能会发生.. .)。我想我快到了,需要阅读更多关于你让我意识到的内容
  • @KLôN - .timeout() 不是 ES6 标准承诺功能。一些 Promise 库支持它。如果您的问题实际上包含您真正想要保护的内容,那么您会从我这里得到更好的答案。要求更好的脚手架并不能告诉我们您想要什么。
  • 你是完全正确的。事实是:我问了一个非常糟糕的问题,因为我无法掌握我真正想要的东西!不管你怎么想,你的 cmets 和回答对我帮助很大。我接受你的回答,这很有帮助。您能否花点时间阅读我写的答案并告诉我更新的代码是否看起来不错?非常感谢您的时间和帮助!
猜你喜欢
  • 1970-01-01
  • 2015-01-16
  • 2011-11-15
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-13
相关资源
最近更新 更多