Reputation: 3251
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?
Upvotes: 2
Views: 404
Reputation: 2727
Use the async npm module. They provide synchronous iteration methods as well as different methods for control flow such as series, waterfall, parallel etc.
Upvotes: 0
Reputation: 1074208
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.)
Upvotes: 1
Reputation: 214949
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
).
Upvotes: 2