Review async/await
Finding files/directories with glob
API documentation
package.json
come from, and when is it updated?public
directory and where is it relative to app.js
?More about example project directories
fs
functions?Read files with fs.readFile
Write files with fs.writeFile
/fs.appendFile
Read directories with fs.readdir
What's the output?
// this function intends to log active users to a single file.
// in this case, we are worried about the order, so ensure it is preserved.
function logUsers(clientList) {
for (let client of clientList) {
fs.writeFile("user-log.txt", client, "utf8", (err) => {
if (err) {
console.error(err + "\nFailed to log user.");
}
});
}
}
logUsers(["Tal", "Manny", "Hudson"]);
JS
First, if we want to append each name, we should use fs.appendFile
instead of fs.writeFile
That still won't solve the problem of order though
This is an example where async
/await
come in handy!
There are a few ways we can use async/await with fs
, but the key thing is we need to use await
on functions that return Promises
fs.readFile
and fs.writeFile
are asynchronous but do not return Promises
We could make our own Promise-returning functions, but that can be tedious.
Instead, we'll need to "promisify" these functions so we can use async
and await
The core utils
package comes with a function promisify
which we can use to return a Promise-returning version of a function
Let's practice with the previous example (and update it to use fs.appendFile
).
const fs = require("fs");
const util = require("util");
async function logUsers(clientList) {
const appendFile = util.promisify(fs.appendFile);
for (let client of clientList) {
try {
await appendFile("user-log.txt", client + "\n");
} catch (err) {
console.error(err + "\nFailed to append file with user: " + client);
}
}
}
logUsers(["Tal", "Manny", "Hudson"]);
JS
readFile
3 WaysThe following functions are three ways to read a file. Note that all are good solutions, but
the more asynchronous fucntions you work with in your functions, the more useful it is to use async
/await
with
promisified functions.
const fs = require("fs");
const util = require("util");
const readFile = util.promisify(fs.readFile);
function readFileCallback() {
// Syntax: readFile(fileName, (error, result) callback)
fs.readFile("data/example.txt", "utf8", (err, contents) => {
if (err) {
handleError(err);
} else {
printContents(contents);
}
});
}
// Promisified version
function readFilePromisified() {
const readFilePromise = readFile("data/example.txt", "utf8");
readFilePromise
.then(printContents)
.then(() => { console.log("Done reading file!") })
.catch(handleError);
}
async function readFileAsync() {
try {
const contents = await readFile("data/example.txt", "utf8");
printContents(contents);
console.log("Done reading file!");
} catch (err) {
handleError(error);
}
}
function printContents(contents) {
console.log("File contents:");
console.log(contents);
}
function handleError(err) {
console.log("There was an error: " + err);
}
readFileCallback();
readFilePromisified();
readFileAsync();
JS
async
/await
For error-handling with async/await, you must use try/catch instead of .then/.catch
The catch statement will catch any errors that occur in the then block (whether it’s in a Promise or a syntax error in the function), similar to the .catch in a fetch promise chain
Remember that if you are using these in a web service, you should handle client-specific (400) errors differently than server-specific (500) errors (such as those caught by a fs
function).
fs.readdir
returns an array of files within a given directory path
fs.readdir("data", (err, paths) => {
if (err) {
console.error("Error reading directory");
} else {
console.log("data directory contents: ");
console.log(contents);
}
});
JS (standard callback version)
const readdir = utils.promisify(fs.readdir);
try {
const paths = await readdir("data");
console.log("data directory contents: ");
console.log(paths);
} catch (err) {
console.error("Error reading directory");
}
JS (async/await version)
Example code: directory-reading.js
Sometimes you will want to find files and directories in your Node.js programs
This is useful particularly when writing scripts, as well as extensible APIs that rely on directory structures (e.g. returning a list of all menu categories based on non-empty directories)
What if we could match patterns to look for certain files and directories?
We can't with fs
, but there are various modules to help
How do we pick a new module to use in our Node.js projects?
There are many modules we can use in Node.js in addition to the core modules
"glob" is a method that many programming languages use to process the file system with regex-like patterns
Node.js has many glob modules
A very useful site to choose modules is npmtrends.com
We will use glob
since it has most support and is very easy to get started with.
glob
glob(pattern, [options], (err, matches) => { ... });
JS (syntax)
glob("*.png", (err, matches) => {
if (err) {
console.error("There was an error: " + err);
} else {
console.log(".png images in current directory:", matches);
}
});
JS
Check out some useful documentation of more features here
Examples: glob-examples.js
Since glob takes a callback as its last argument, we can use utils.promisify
to
define a promise-returning version.
const globPromise = util.promisify(glob);
async function globAsync() {
try {
let matches = await globPromise("data/*");
console.log(".png images in current directory:", matches);
} catch(err) {
console.error("There was an error: " + err);
}
}
JS
Often, web services don't rely on hard-coding data in files
For example, our menu web service returns JSON, but what if we want to support different JSON responses?
{
"categories" : {
"Drinks" : [
{
"name" : "Classic coffee",
"description" : "The classic.",
"image" : "coffee.png",
"in-stock" : true
}, ...
],
"Foods" : [
...
]
}
We might want to keep track of item quantity for example, and uses that to determine the in-stock key
Let's take a closer look at the new directory structure, and get a bit more practice with glob
The cafe web service now holds its data in a directory structure to help process the results
cse154-cafe/
app.js
categories/
Drinks/
bubble-tea/
info.txt
purchase-history.txt
classic-coffee/
info.txt
purchase-history.txt
...
Foods/
...
public/
fetch-menu.js
admin.js
img/
stock-img/
...
Contents (abbreviated)
let categoryPaths = await globPromise("categories/*");
// ["categories/Foods", "categories/Drinks"]
let publicFolders = await globPromise("public/*/");
// ["public/img/"]
let drinksInfo =
await globPromise("categories/Drinks/*/info.txt");
// ["categories/Drinks/bubble-tea/inf.txt",
// "categories/Drinks/classic-coffee/info.txt",
// "categories/Drinks/the-sippy/info.txt"]
// ** recursively searches within the current directory
// and all subdirectories
let allJS = await globPromise("**/*.js");
// ["app.js", "public/fetch-menu.js", "public/admin.js"]
// can use [patt1|patt2|...] to match patt1 or patt2 or ...
let allImages = await globPromise("**/*.[png|jpg]");
JS
Like many other fs
functions, there is an optional "options" parameter to the glob
function to support
various useful options
To use, just pass an object as the second argument with the option(s) you want as keys.
// the nodir option ignores directories
let dataFiles = await globPromise("data/*", { "nodir" : true });
// the ignore option takes patterns to ignore
let noGifs = await globPromise("public/img/*", { "ignore" : ['*png'] });
At minimum, all route functions must be documented (1-2 sentences) and your overall file header should include a summary of all endpoints (with their response formats/possible errors)
If you have API documentation, it can be much easier to refer to in your header comments
In HW4, you will not be submitting an APIDOC.md, so your file header should serve as an overview of the endpoints
In CP4 and the Final Project, you are required to submit APIDOC.md
There are provided templates (APIDOC.md), but you are free to modify it based on what you like most.
If you would like to use an API documentation tool generator (such as POSTMan or Swagger), you are free to do so, but must receive permission from the instructor to receive credit in your submission in place of APIDOC.md.
We'll get more practice in section tomorrow!
Tomorrow, you will get practice with using file-processing, async/await, glob, and API documentation.
On Friday, we will learn how to handle POST requests and how to deploy our Node APIs on a real server!