0

I still cannot grasp how to solve this. I tried solutions that were given in the How do I add a delay in a JavaScript loop? that supposedly has answers for my question but they have not worked for me.

Either I get the same thing that I tried (one wait function that runs once and that does not solve my problem). Or upon running the script, the script finishes with no output neither in the cli, nor in the Result.txt file that I want my results to be written into.

var path = require('path');
var fs = require('fs');
var VirusTotalApi = require("virustotal-api");
var virusTotal = new VirusTotalApi('<YOUR API KEY>');

fs.readdir('/home/username/Desktop/TEST/', function (err,files) {
  if (err) {
    return console.log('Unable to scan directory: ' +err);
  }

  files.forEach(function (file) {
    var directoryPath = path.join('/home/username/Desktop/TEST/', file);

    fs.readFile(directoryPath, (err, data) => {
      if (err) {
        console.log(`Cannot read file. ${err}`);
      } else {
        console.log(file);
        virusTotal
        .fileScan(data, `${file}`)
        .then(response => {
          let resource = response.resource;
      virusTotal.fileReport(resource).then(result => {
            fs.writeFile('Result.txt', `${file}: ` + JSON.stringify(result, ["verbose_msg","total","positives"]) + '\n', function (err) {
              if (err) throw err;
              if(!err) {
                console.log(`${file}: Saved!`);
              }
            }); 
          });
        })
        .catch(err => console.log(`Scan failed. ${err}`));
        if(!err) {
          console.log('Scan succesful');
        }
      }
    });
  });
});

Once more, I need a ForEach loop that would wait for 20 seconds, and then run the desired code. It would also need to wait before going on to the next for condition.

jerkdavi
  • 47
  • 2
  • 9
  • 1
    What is reason for exactly **20 seconds** wait time? Are you waiting for your required operations to take place and assuming 20 seconds would be the time required? – deep206 Jun 04 '21 at 16:00
  • Deer deep, yes. As I mentioned in my first question (https://stackoverflow.com/questions/67838518/foreach-async-js), that was closed prematurely, limit of free API key is 4 filescans per minute. So, I would need to do a ForEach loop that would wait for 20 seconds, and then run desired code. In actuality I discovered uploading part can be run simultaneously without need for waiting between files. I can push xy files for scan and I have a script that does just that. Then it waits 100 secs but that is just because if I request result immediately I just get 'Your resource is queued for analysis'. – jerkdavi Jun 05 '21 at 14:40
  • Requesting results part is one that is limited in how many times you can run it per minute. If i run it too quickly, I get an error. Therefore, the need for 20 seconds, that was updated to 30 seconds and now works like a charm. – jerkdavi Jun 05 '21 at 14:42

2 Answers2

3

You don't solve asynchronous issues by inserting pauses and you can't pause a .forEach() loop anyway. Instead, you need to write the type of asynchronous code that gives you control over the sequencing and completion of your asynchronous operations.

Using promises for all asynchronous operations and avoiding the non-asynchronous-aware loop constructs such as .forEach() will make this a lot easier. Here's one way to approach it:

const path = require('path');
const fsp = require('fs').promises;
const VirusTotalApi = require("virustotal-api");
const virusTotal = new VirusTotalApi('<YOUR API KEY>');

const basePath = '/home/username/Desktop/TEST/';

async function scan() {
    const files = await fsp.readdir(basePath);
    let errors = [];
    for (let file of files) {
        const fullPath = path.join(basePath, file);
        try {
            const data = await fsp.readFile(fullPath);
            const response = await virusTotal.fileScan(data, file);
            const resource = response.resource;
            const result = await virusTotal.fileReport(resource);
            const resultLine = `${file}: ${JSON.stringify(result)}\n`;
            await fsp.appendFile('Result.txt', resultLine);
        } catch (e) {
            // collect the error, log the error and continue the loop
            e.fullPath = fullPath;
            errors.push(e);
            console.log(`Error processing ${fullPath}`, e);
            continue;
        }
    }
    // if there was an error, then reject with all the errors we got
    if (errors.length) {
        let e = new Error("Problems scanning files");
        e.allErrors = errors;
        throw e;
    }
}

scan().then(() => {
    console.log("all done scanning - no errors");
}).catch(err => {
    console.log(err);
});

Also, where you were using fs.writeFile('Result.txt', ...), I changed it to use .appendFile() because it appears you want to accumulate a log of results, not overwrite all previous results each time.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Dear jfriend, your code is really nicely organized. I added next line after const basePath const wait=(time)=>new Promise((resolve)=>setTimeout(resolve, time)); I added next lines after for loop const sata=await fsp.readFile('Result.txt'); if(sata.includes(file)){ console.log(`${file} >> already scanned!`);} else{ and then const fullPath continues I added next line before for loop end await wait(30000); Code works perfectly. I have a new problem. If file was not scanned, I want it to wait 30 secs. If file was scanned I do not want it to wait 30 secs. Can it be done? Thank you. – jerkdavi Jun 05 '21 at 14:24
  • I am sorry for not responding yesterday. I used up all 500 scans for the day, so I could not test out your solution. – jerkdavi Jun 05 '21 at 14:28
1

Based on the https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

As I see it is impossible to wait for 20 seconds between each callback call by using forEach function. The forEach does not work with Promises and thus it is impossible to wait inside of it.

There is exists a solution, it uses for loops and async container.

var path = require('path');
var fs = require('fs');
var VirusTotalApi = require("virustotal-api");
var virusTotal = new VirusTotalApi('<YOUR API KEY>');

const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))

fs.readdir('/home/username/Desktop/TEST/', async function (err,files) {
  if (err) {
    return console.log('Unable to scan directory: ' +err);
  }

  for (let file of files) {
    var directoryPath = path.join('/home/username/Desktop/TEST/', file);

    fs.readFile(directoryPath, (err, data) => {
      if (err) {
        console.log(`Cannot read file. ${err}`);
      } else {
        console.log(file);
        virusTotal
        .fileScan(data, `${file}`)
        .then(response => {
          let resource = response.resource;
      virusTotal.fileReport(resource).then(result => {
            fs.writeFile('Result.txt', `${file}: ` + JSON.stringify(result, ["verbose_msg","total","positives"]) + '\n', function (err) {
              if (err) throw err;
              if(!err) {
                console.log(`${file}: Saved!`);
              }
            }); 
          });
        })
        .catch(err => console.log(`Scan failed. ${err}`));
        if(!err) {
          console.log('Scan succesful');
        }
      }
    });

    // Wait for 20 seconds
    await wait(20 * 1000)
  }
});
  • Dear Nikita, I added these lines after for loop: fs.readFile('Result.txt', async function (err, sata){ if (err) throw err; if(sata.includes(file)){ console.log(`${file}>>already scanned!`);} else{...and then var directoryPath continues I also changed time from 20 to 30 secs. Code works perfectly. I have a new problem. Part that I added checks file Result.txt for file name, so it does not need to scan same files again. And that part works well. Problem is that if file was already scanned, I do not want it to wait 30 secs but continue immediately. Can it be done? Спасибо – jerkdavi Jun 05 '21 at 14:16
  • I am sorry for not responding yesterday. I used up all 500 scans for the day, so I could not test out your solution. – jerkdavi Jun 05 '21 at 14:28