首页 > 解决方案 > 下载多个 SFTP 文件时出现 NodeJS 错误“检测到可能的 EventEmitter 内存泄漏。添加了 11 个错误侦听器”

问题描述

ssh2-sftp-client使用库从 SFTP 站点下载多个文件时出现错误。抛出的错误似乎表明每次下载完成后节点流没有被清除。这导致我的应用程序出现内存泄漏。在生产中,我需要能够下载数千个文件,因此这种内存泄漏非常严重。如何关闭流以便在下载每个文件后释放内存?

代码:

const Client = require('ssh2-sftp-client');

const sftp = new Client();
sftp.connect({
  host: '195.144.107.198',
  port: 22,
  username: 'demo',
  password: 'password'
}).then(async () => {

  const fileNames = ['readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt'];

  // Loop through filenames
  for (let i = 0; i < fileNames.length; i++) {

    // Download all the files synchronously (1 at a time)
    const fileName = fileNames[i];
    await new Promise((resolve, reject) => { // <-- note the await
      sftp.get(fileName, true, 'utf8').then((stream) => {
        let text = '';
        stream
          .on('data', (d) => { text += d; })
          .on('end', () => {
            console.log('Success downloaded file', i);
            resolve(text);
          });
      }).catch((err) => {
        console.log('Error downloading file', err);
        reject(err.message)
      });
    });
  }
  sftp.end();
});

注意:此代码使用公共 SFTP 站点,因此凭据不敏感,您可以运行它进行测试。在这里找到:https ://www.sftp.net/public-online-sftp-servers

错误(在文件 #9 下载后发生):

(node:44580) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 error listeners added. Use emitter.setMaxListeners() to increase limit

标签: javascriptnode.jssftpnode-streams

解决方案


因此,您说您正在尝试在 prod 中下载数千个文件,但您正在为每个文件使用一个侦听器。Node 仅允许您10在触发警报之前创建最大的事件侦听器。

看:

https://nodejs.org/dist/latest-v8.x/docs/api/events.html#events_eventemitter_defaultmaxlisteners https://github.com/nodejs/help/issues/1051

如果你想纠正这个问题,我建议你实现一个queue并且一次只下载 10 个文件。

就像是:

const Client = require('ssh2-sftp-client');

const sftp = new Client();
sftp.connect({
  host: '195.144.107.198',
  port: 22,
  username: 'demo',
  password: 'password'
}).then(async () => {

  // Treat files array as a queue instead of an array
  const fileQueue = ['readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt', 'readme.txt'];

  // Use this function to grab files from your main files array
  const downloadFilesFromQueue = (fileName) =>
      new Promise((resolve, reject) => {

      // Sanity check
      if(!fileName) {
          resolve();
      }

      sftp.get(fileName, true, 'utf8').then((stream) => {
        let text = '';
        stream
          .on('data', (d) => { text += d; })
          .on('end', () => {
            console.log('Success downloaded file', fileName);
            resolve(text);
          });
      }).catch((err) => {
        console.log('Error downloading file', err);
        reject(err.message);
      });
    })

    // Handle errors
    .catch((err) => console.log(err.message))

    // Get next file from the queue
    .then(() => {

       // If there are no more items in the queue, we're done
       if (!fileQueue.length) {
           return;
       }

       downloadFilesFromQueue(fileQueue.shift())
    });

  // Track all unresolved promises
  const unresolvedPromises = [];

  // Request no more than 10 files at a time.
  for (let i = 0; i < 10; i++) {

    // Use file at the front of the queue
    const fileName = fileQueue.shift();

    unresolvedPromises.push(downloadFilesFromQueue(fileName));
  }

  // Wait until the queue is emptied and all file retrieval promises are 
  // resolved.
  await Promise.all(unresolvedPromises);

  // done
  sftp.end();
});

推荐阅读