2

that's my javascript code so far:

files is an array of paths to html files

I want h3s, to be an array of all h3s tags of the html files.

function getH2OfFiles(files) {

  return new Promise((resolve, reject) => {
    let h3s = [];
    for (let i = 0; i < files.length; i++) {
      fs.readFile(path.join(__src, 'documentation', files[i]), 'utf8', (err, data) => {
        if (err) throw err;
        if (data.match(/<h3>(.*)<\/h3>/)) {
          //console.log("->", { file: files[i], h3: data.match(/<h3>(.*)<\/h3>/)[1] })
          h3s.push(data.match(/<h3>(.*)<\/h3>/)[1]);
        }
      })

    }
    resolve(h3s);
  });
}

It does not seem to work in a for loop (because it's asynchronous), but how is it realizable?

hardfork
  • 2,470
  • 1
  • 23
  • 43

3 Answers3

2

A counterpart for the for loop in the promise world is Promise.all, often combined with .map. In your case, write a function that handles one file, e.g

function getH3OfFile(fileName) {
    return new Promise((resolve, reject) => {
        fs.readFile(path.join('.....', fileName), 'utf8', (err, data) => 
            err
                ? reject(err)
                : resolve(data.match(/<h3>(.*)<\/h3>/))
        );
    });
}

and then apply it to the list of files:

let fileNames = [...]

Promise
   .all(fileNames.map(getH3OfFile))
   .then(h3s => ...) // h3s is an array of matches

(Just in case you're not aware, there's also readFileSync).

georg
  • 211,518
  • 52
  • 313
  • 390
1

You're most of the way there, you just have to keep track of how many callbacks you've gotten and wait to resolve until you've gotten them all. Also, use reject(err) rather than throw err if there's a problem:

function getH2OfFiles(files) {

  return new Promise((resolve, reject) => {
    let h3s = [];
    let complete = 0;
    for (let i = 0; i < files.length; i++) {
      fs.readFile(path.join(__src, 'documentation', files[i]), 'utf8', (err, data) => {
        if (err) reject(err);                       // ***
        const match = data.match(/<h3>(.*)<\/h3>/);
        if (match) {                                // ***
          h3s.push(match[1]);
        }
        if (++complete === files.length) {          // ***
          resolve(h3s);                             // ***
        }                                           // ***
      })
    }
  });
}

(Note that I've also saved the result of the first call to match rather than making the regex run twice.)

But, beware that you can receive those completions out of order, so h3s may be out of order with the files (if it matters).

Or simplify through divide-and-conquer and give yourself a Promise-ified version of readFile and then build the results once all files are read, via Promise.all:

function readFilePromise(path, encoding) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, encoding, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

function getH2OfFiles(files) {
  return Promise.all(files.map(file => readFilePromise(path.join(__src, 'documentation', file), 'utf8')))
         .then(results => {
           const h3s = [];
           results.forEach(result => {
             const match = data.match(/<h3>(.*)<\/h3>/);
             if (match) {
               h3s.push(match[1]);
             }
           });
           return h3s;
         });
}

This also has the advantage that Promise.all ensures you receive the array in the same order as the original array of promises (if it matters).

(Note: There are libs that will Promise-ify NodeJS APIs for you.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Use the async npm module. They provide synchronous iteration methods as well as different methods for control flow such as series, waterfall, parallel etc.

Abhishek Singh
  • 2,642
  • 15
  • 24