NodeJS: Wait for all foreach with Promises to finish but never actually finishes

Viewed 2056

I am working with Nodejs. I have a forEach which is async as I have to wait for a result inside the forEach. As a result, I need to wait for the forEach to finish and then carry on with the result of the loop. I found several solutions for waiting for the forEach, one of them is using Promises. I did though, and these promises are created, however, the code after the forEach (and therefore the promises) are finished, is never actually executed (console.log is not printed). And the NodeJS function just ends without any errors.

Here is my Code:

var Client = require('ssh2').Client;

// eslint-disable-next-line no-undef
var csv = require("csvtojson");
// eslint-disable-next-line no-undef
var fs = require("fs");
// eslint-disable-next-line no-undef
const config = require('./config.json');
// eslint-disable-next-line no-undef
const os = require('os');
let headerRow = [];
let sumTxAmount = 0;

const filenameShortened = 'testFile';

let csvLists = [];
let csvFile;

const options = {
    flags: 'r',
    encoding: 'utf8',
    handle: null,
    mode: 0o664,
    autoClose: true
}

var conn = new Client();

async function start() {
    const list = await getCSVList();
    let content = fs.readFileSync('./temp.json', 'utf8');
    content = JSON.parse(content);
    var promises = list.map(function(entry) {
        return new Promise(async function (resolve, reject) {
            if (!content['usedFiles'].includes(entry.filename)) {
                const filename = entry.filename;
                csvFile = await getCsv(filename);
                csvLists.push(csvFile);
                console.log('here');
                resolve();
            } else {
                resolve();
            }
        })
    });
    console.log(promises)
    Promise.all(promises)
        .then(function() {
            console.log(csvLists.length, 'length');
        })
        .catch(console.error);
}

start();

The "here" is printed once (not 8 times as the arrays length is 8), but there are 8 promises created. The lower part where I am printing the length of the array is not executed.

Can anyone tell me what I am doing wrong? Am I using Promises and forEach falsely as I have to do an await inside the forEach?

Note: getCSVList() and getCsv() are functions to get Csvs from an sftp server:

function getCSVList() {
    return new Promise((resolve, reject) => {
            conn.on('ready', function () {
                conn.sftp(function (err, sftp) {
                        if (err) throw err;
                        sftp.readdir(config.development.pathToFile, function (err, list) {
                            if(err) {
                                console.log(err);
                                conn.end();
                                reject(err);
                            } else {
                                console.log('resolved');
                                conn.end();
                                resolve(list);
                            }
                        })
                })
            }).connect({
                host: config.development.host,
                port: config.development.port, // Normal is 22 port
                username: config.development.username,
                password: config.development.password
                // You can use a key file too, read the ssh2 documentation
            });
    })
}

function getCsv(filename) {
    return new Promise((resolve, reject) => {
        conn.on('ready', function () {
        conn.sftp(function (err, sftp) {
            if (err) reject(err);
            let csvFile = sftp.createReadStream(`${config.development.pathToFile}/${filename}`, options);
            // console.log(csvFile);
            conn.end();
            resolve(csvFile);
        })
    }).connect({
        host: config.development.host,
        port: config.development.port, // Normal is 22 port
        username: config.development.username,
        password: config.development.password
        // You can use a key file too, read the ssh2 documentation
    });
});
} 

The output in my console from all the console logs is:

`➜ node server.js
resolved
[ Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> } ]
here`
2 Answers

Promise.all is a method that will return a promise object, but you are not waiting for your start method to execute.

function getCSVList() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([1, 2, 3, 4]);
    }, 1000);
  });
}

function getCsv(params) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(params);
    }, 1000);
  });
}

async function start() {
  const list = await getCSVList();
  const promises = list.map(item => {
    return new Promise(async function (resolve, reject) {
      const csvFile = await getCsv(item);
      console.log('here');
      resolve(csvFile);
    });
  });

  return Promise.all(promises);
}

start().then(res => {
  console.log(res);
});


Break up your problem into pieces, confirming they work along the way.

You are not using the stream correctly, among other things.

I made a working example with ssh2-sftp-client so you can maybe use it as a starting point.


Working example :

var fs = require('fs'); var _ = require('underscore');
var SFTPClient = require('ssh2-sftp-client');
const CONFIG = {
 "SSH_CONN_OPTS":{"host":"XXXXXXXX","port":22,"username":"XXXXXXXX","password":"XXXXXXXX"},
 "CSV_DIRECTORY":"/var/www/html"
}
//---------------
//.:The order-logic of the script is here
function StartScript(){
 console.log("[i] SSH Connection")
 LoadValidationFile(()=>{
  InitializeSFTP(()=>{ console.log("[+] SSH Connection Established")
   ListRemoteDirectory((list)=>{ console.log(`[i] Total Files @ ${CONFIG.CSV_DIRECTORY} : ${list.length}`)
    //console.log(list) //:now you have a 'list' of file_objects, you can iterate over to check the filename
    var csvFileList = [] //store the names of the files you will request after
    _.each(list,(list_entry)=>{ console.log(list_entry)
     if(!CONFIG.USED_FILES.includes(list_entry.name)){ csvFileList.push(list_entry.name) }
    }) 
    //:now loop over the new final list of files you have just validated for future fetch 
    GenerateFinalOutput(csvFileList)
   })
  })
 })
}
//.:Loads your validation file
function LoadValidationFile(cb){
 fs.readFile(__dirname+'/temp.json','utf8',(err,data)=>{ if(err){throw err}else{
  var content = JSON.parse(data)
  CONFIG.USED_FILES = content.usedFiles
  cb()
 }})
}
//.:Connects to remote server using CONFIG.SSH_CONN_OPTS
function InitializeSFTP(cb){
 global.SFTP = new SFTPClient();
 SFTP.connect(CONFIG.SSH_CONN_OPTS)
 .then(()=>{cb()})
 .catch((err)=>{console.log("[!] InitializeSFTP :",err)})
}
//.:Get a list of files from a remote directory
function ListRemoteDirectory(cb){
 SFTP.list(`${CONFIG.CSV_DIRECTORY}`)
     .then((list)=>{cb(list)})
     .catch((err)=>{console.log("[!] ListRemoteDirectory :",err)})
}
//.:Get target file from remote directory
function GetRemoteFile(filename,cb){
 SFTP.get(`${CONFIG.CSV_DIRECTORY}/${filename}`)
     .then((data)=>{cb(data.toString("utf8"))}) //convert it to a parsable string
     .catch((err)=>{console.log("[!] ListRemoteDirectory :",err)})
}
//-------------------------------------------
var csvLists = []
function GenerateFinalOutput(csv_files,current_index){ if(!current_index){current_index=0}
 if(current_index!=csv_files.length){ //:loop
  var csv_file = csv_files[current_index]
  console.log(`[i] Loop Step #${current_index+1}/${csv_files.length} : ${csv_file}`)
  GetRemoteFile(csv_file,(csv_data)=>{
   if(csv_data){csvLists.push(csv_data)}
   current_index++
   GenerateFinalOutput(csv_files,current_index)
  })
 }else{ //:completed
  console.log("[i] Loop Completed")
  console.log(csvLists)
 }
}
//------------
StartScript()

Good luck!

Related