This one drove me nuts, I’ll admit it. The directions were sparse, but eventually I figured it out. You can’t just copy-paste from the previous exercise where we filtered I/O, though at first it seems like you can. Turns out there’s a lot of extras thrown into this one, such as error checking.
Okay, let’s look at the Instructions and hints:
MAKE IT MODULAR (Exercise 6 of 13)
This problem is the same as the previous but introduces the concept of modules. You will need to create two files to solve this.
Create a program that prints a list of files in a given directory, filtered by the extension of the files. The first argument is the directory name and the second argument is the extension filter. Print the list of files (one file per line) to the console. You must use asynchronous I/O.
You must write a module file to do most of the work. The module must export a single function that takes three arguments: the directory name, the filename extension string and a callback function, in that order. The filename extension argument must be the same as what was passed to your program. Don’t turn it into a RegExp or prefix with “.” or do anything except pass it to your module where you can do what you need to make your filter work.
The callback function must be called using the idiomatic node(err, data) convention. This convention stipulates that unless there’s an error, the first argument passed to the callback will be null, and the second will be your data. In this exercise, the data will be your filtered list of files, as an Array. If you receive an error, e.g. from your call to fs.readdir(), the callback must be called with the error, and only the error, as the first argument.
You must not print directly to the console from your module file, only from your original program.
In the case of an error bubbling up to your original program file, simply check for it and print an informative message to the console.
These four things are the contract that your module must follow.
» Handle all the errors that may occur and pass them to the callback.
The benefit of having a contract is that your module can be used by anyone who expects this contract. So your module could be used by anyone else who does learnyounode, or the verifier, and just work.
## HINTS
Create a new module by creating a new file that just contains your directory reading and filtering function. To define a single function export, you assign your function to the module.exports object, overwriting what is already there:
module.exports = function (args) { /* … */ }
Or you can use a named function and assign the name.
To use your new module in your original program file, use the require() call in the same way that you require(‘fs’) to load the fs module. The only difference is that for local modules must be prefixed with ‘./’. So, if your file is named mymodule.js then:
var mymodule = require(‘./mymodule.js’)
The ‘.js’ is optional here and you will often see it omitted.
You now have the module.exports object in your module assigned to the mymodule variable. Since you are exporting a single function, mymodule is a function you can call!
Also keep in mind that it is idiomatic to check for errors and do early-returns within callback functions:
}
Let’s tackle the module first. Basically, by module, we mean a separate file. The hints give you module.exports = function (args) { /* … */ }, except it turns out that’s not as helpful as it looks.
First thing to realize is that since you’re going to be calling the fs.readdir and path packages, you need them to be required at the beginning of the file. That’s the easy part!
Next, we add the line given above. Now, you see the part in parentheses that says args? Yeah, well, the directions state that you need to have the module export a function that takes THREE arguments and that is very clearly only ONE. So, what would those arguments be? The directions tell us: “the directory name, the filename extension string and a callback function, in that order.”
So, let’s add those:
var fs = require('fs'); var path = require('path'); module.exports = function (filename, extsn, callback) { });
So far, so good. But, what do we put inside that function?
First, the directions tell us not to modify the extension in the main program, and we know from the last exercise that we’ll need a dot in front of it when we run the path.extname() method. So let’s add a dot:
extsn = "." + extsn;
Next, we need to read the directory. We did this in the last assignment, so we’ll just copy that over:
fs.readdir(filename, function callback(err, list) { /* do stuff here */ });
We need to handle any errors that pop up, so we’ll add the error catching routine the specify above:
fs.readdir(filename, function callback(err, list) { if(err) { return callback(err); } /* do stuff here */ });
If there’s no error, we want to run the filtering routine. I had the routine add the files that matched extensions to a new array, called ansArray (Answer Array) and the function returns that. The filter is similar to what we did before, using a loop and comparing the extension retrieved by path.extname() to the extsn variable we passed to the function. If they match, we push the item into the array. So, we have:
fs.readdir(filename, function callback(err, list) { if(err) { return callback(err); } else { for (var item = 0; item < list.length; item++) { if (path.extname(list[item]) === extsn) { ansArray.push(list[item]); } } /* do stuff here */ });
To return ansArray, we use the other line they give us in the hints – callback(null, data):
return callback(null, ansArray);
That’s it. Here’s the complete mymodule.js :
var fs = require('fs'); var path = require('path'); var ansArray = []; module.exports = function (filename, extsn, callback) { extsn = "." + extsn; fs.readdir(filename, function (err, list) { if(err) { return callback(err); } else { for (var item = 0; item < list.length; item++) { if (path.extname(list[item]) === extsn) { ansArray.push(list[item]); } } return callback(null, ansArray); } }); };
After all that, the main program itself is relatively simple. First, we assign the directory path (element[2] of process.argv) to a variable called filename (because I never got around to changing it from the previous exercise), and then we assign the extension for which we are hunting (element[3] of process.argv) to a variable called extsn. Finally, as noted in the hints, we require mymodule.js.
Next, we call mymodule, which is a single function, and we call it with three arguments, as specified. We send it the directory path, the extension on which to filter, and a callback function. We handle any error it passes back to us. Then we print out the results using a loop and iterating through list. Here’s the program:
PROGRAM (program5.js)
var filename = process.argv[2]; var extsn = process.argv[3]; var mymodule = require('./mymodule.js'); mymodule(filename, extsn, function(err, list) { if (err) { console.log(err); } for (var j = 0; j < list.length; j++) { console.log(list[j]); } });
And there we go! See you next time!