【问题标题】:Are callbacks for requests a bad practice in node.js?在 node.js 中,请求的回调是一种不好的做法吗?
【发布时间】:2023-03-19 16:14:01
【问题描述】:

假设您想下载图像或文件,这将是互联网教您继续前进的第一种方式:

request(url, function(err, res, body) {
    fs.writeFile(filename, body);
});

但是这不是把body中的所有数据都累积起来,填满内存吗? pipe 会更高效吗?

request(url).pipe(fs.createWriteStream(filename));

或者这是在内部以类似的方式处理,无论如何缓冲流,使其无关紧要?

此外,如果我想使用回调但body(因为你仍然可以pipe),这个内存缓冲区还会被填满吗?

我问是因为第一个(回调)方法允许我链接下载而不是并行启动它们(*),但我不想填充我也不会使用的缓冲区。因此,如果我不想诉诸诸如 async 之类的花哨的东西只是为了使用 queue 来防止这种情况,我需要回调。

(*) 这很糟糕,因为如果您只是在完成之前request 太多文件,request 的异步性质将导致节点在过量事件和内存丢失中窒息而死。首先你会得到这些:

"possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit."

当拉伸它时,500 个管道请求将填满你的内存并导致节点崩溃。这就是为什么你需要回调而不是管道,所以你知道什么时候开始下一个文件。

【问题讨论】:

  • 管道会更有效,因为它会在数据可用时将数据流式传输到WriteStream。不过,我不认为你的最后一句话是真的。
  • 好像是这样。尝试下载 50 个文档。您将得到 “检测到可能的 EventEmitter 内存泄漏。添加了 11 个侦听器。使用emitter.setMaxListeners() 增加限制。” 下载500,您的内存将填满并崩溃节点。这就是为什么你需要回调而不是管道,所以你知道什么时候开始下一个文件。
  • Resandro,我认为您混淆了流式传输、回调和流控制。流控制将正确维护传出连接的限制(请参阅 async.queue、async.eachLimit 等),但在回调/缓冲范例和流范例中,您需要流控制和资源管理。这些主题是不同的。

标签: node.js request pipe


【解决方案1】:

但这不是将所有数据都累积在正文中,填满内存吗?

是的,很多操作比如你先sn -p 缓冲数据到内存中进行处理。是的,这会使用内存,但至少很方便,有时需要,具体取决于您打算如何处理该数据。如果您想加载 HTTP 响应并将正文解析为 JSON,这几乎总是通过缓冲来完成,尽管使用流式解析器可以做到这一点,但它要复杂得多,而且通常是不必要的。大多数 JSON 数据都不够大,因此流式传输是一个巨大的胜利。

或者这是在内部以类似的方式处理,因此无关紧要?

不,将整段数据作为字符串提供给您的 API 使用缓冲而不是流式传输。

但是,多媒体数据,是的,您实际上无法将其缓冲到内存中,因此流式传输更合适。此外,这些数据往往是不透明的(您不会对其进行解析或处理),这也有利于流式传输。

在情况允许的情况下,流式传输很好,但这并不意味着缓冲必然有任何问题。事实是缓冲是绝大多数事情大部分时间的工作方式。从总体上看,流式传输只是一次缓冲 1 个块,并将它们限制在可用资源范围内的某个大小限制。如果您要处理它,部分数据需要在某个时候通过内存。

因为如果你只是一个一个地请求太多的文件,请求的异步性会导致节点在事件过量和内存丢失的情况下窒息而死。

不确定您在此陈述/询问的确切内容,但是是的,编写有效的程序需要考虑资源和效率。

另见substack's rant on streaming/pooling in the hyperquest README

【讨论】:

  • 你一开始还不错,但你的贬低语气和开门的例子是-1。如果您可以消除对这个问题和明显的缓冲限制陈述的不满,并提供一些有用的东西,我将删除反对票。
  • 更好,虽然我不介意时髦点。关于节点阻塞的最后引用实际上不是一个问题,这就是我问上一个问题的原因:“如果我想使用回调但不使用正文(因为你仍然可以管道),这个(缓冲的)内存还会被使用(浪费)吗?” 因为如果我不想诉诸async 之类的花哨的东西只是为了使用queue 来防止这种情况,我需要回调。我认为答案是肯定的,所以我正在寻找替代方案。
  • 您错过了流式传输响应或缓冲它们不会影响您有多少并发请求待处理的观点。您想要做的是了解何时有“足够”的待处理请求和 WAIT,然后再发出更多请求。这是关于控制您启动多少 I/O,而不是您是否缓冲或流式传输响应。
  • 例如,您甚至可以使用一个简单的整数来跟踪有多少请求处于活动状态,当达到阈值时,不要启动任何新请求,直到它回落到阈值以下。在缓冲和流式 API 中都可以。或者你可以使用async.queue,它本质上就是这样做的。
  • 好吧,您似乎误解了我的意思,因为我已经表明我希望一次有一个待处理的请求,并在前一个请求完成后启动下一个请求。这就是为什么我需要回调,但 不需要 缓冲。 async.queue 是我自己提出的解决方案,但我更喜欢保持轻松。
【解决方案2】:

我想出了一个解决方案,使有关记忆的问题变得无关紧要(尽管我仍然很好奇)。

如果我想使用回调但body(因为你仍然可以pipe),这个内存缓冲区还会被填满吗?

您不需要来自request()callback 即可知道请求何时完成。当stream“结束”时,pipe() 将自行关闭。关闭会发出一个事件并且可以被监听:

request(url).pipe(fs.createWriteStream(filename)).on('close', function(){           
    next();
});

现在您可以将所有请求排队并一个一个下载文件。

当然,您可以使用 async.queue 等库始终使用 8 个并行请求来清理 Internet,但如果您只想使用简单的脚本获取一些文件,async 可能是矫枉过正。

此外,无论如何,您都不会想在多用户系统上使用一个技巧来最大化您的系统资源。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-10
    • 2019-01-24
    • 1970-01-01
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多