下载多个SFTP文件时出现NodeJS错误“检测到可能的EventEmitter内存泄漏。已添加11个错误侦听器”

时间:2018-10-15 21:37:28

标签: javascript node.js sftp node-streams

使用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

1 个答案:

答案 0 :(得分:1)

因此,您说您正在尝试下载产品中的数千个文件,但每个文件都使用了侦听器。 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();
});