Disclaimer: I am trying to learn, so question may be (an probably is) trivial; any comment on better ways to do the same are welcome, but I need to understand underlying concepts.
I am more of a python programmer, so async
/await
is somewhat familiar, while Promise is a rather new concept.
I have a simple Node.js program scanning a subdirectory and doing stuff with filenames:
const chalk = require('chalk');
const ProgressBar = require('progress');
const fs = require('fs');
const path = require('path');
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) yield* await walk(entry);
else if (d.isFile()) yield entry;
}
}
async function scan(cb) {
var docs = [];
for await (const p of walk('docs')) {
if (p.endsWith('.md')) {
console.log(p);
docs.push(p);
}
}
docs.sort();
if (cb)
cb(docs);
}
async function inspect(docs) {
const bar = new ProgressBar(':bar', { total: docs.length });
for (f in docs) {
if (bar.curr === 0) {
console.log(chalk.green('starting...'));
}
bar.tick();
// do other work and (possibly) modify `docs` array
if (bar.complete) {
console.log(chalk.green('done.'));
}
}
// pass docs to next stage
}
scan(inspect)
Since the chain will be much longer than this (I need to read files, order them according to content, build a TOC, etc.) I thought to modify it to use promises to resemble something like:
scan('/my/dir/to/scan')
.then(inspect)
.then(generate, `/path/to/target/file/`)
.catch(printError)
But I couldn't find a way to do it (I saw several answers on the subject, but I couldn't find a real answer to my question).
I would suggest, you should not call the callback functions directly from other functions. Instead, keep your functions simple and return the output. Create a parent function and call all the functions line by line using await
keyword and pass the output. Refer below code:
const chalk = require('chalk');
const ProgressBar = require('progress');
const fs = require('fs');
const path = require('path');
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) yield* await walk(entry);
else if (d.isFile()) yield entry;
}
}
async function scan() {
var docs = [];
for await (const p of walk('docs')) {
if (p.endsWith('.md')) {
console.log(p);
docs.push(p);
}
}
docs.sort();
return docs;
}
async function inspect(docs) {
const bar = new ProgressBar(':bar', { total: docs.length });
for (f in docs) {
if (bar.curr === 0) {
console.log(chalk.green('starting...'));
}
bar.tick();
// do other work and (possibly) modify `docs` array
if (bar.complete) {
console.log(chalk.green('done.'));
}
}
// pass docs to next stage
return docs;
}
async function nextStage(docs){
// do some stuff and return something
}
async function main(){
try {
let docs = await scan();
docs = await inspect(docs);
let nextValue = await nextStage(docs);
// keep adding the calls here. It is more maintainable
} catch(e){
// handle the exception here
}
}
Edit: To do this using Promises, you can refer below code. It is not direct conversion of your code but I hope it simplifies the things and you can understand how to use promises:
async function* walk(dir) {
let files = ['A.txt', 'B.txt', 'A.md', 'B.md', 'c.md'];
for(file of files){
yield file;
}
}
async function scan() {
var docs = [];
for await (const p of walk('docs')) {
if (p.endsWith('.md')) {
console.log(p);
docs.push(p);
}
}
docs.sort();
// return a promise here
return new Promise(function (resolve, reject) {
// Pass the arguments in resolve()
resolve(docs);
// you can also call reject(error) to invoke the catch method
})
}
async function inspect(docs) {
// here I am simply renaming the files
for(let i=0; i<docs.length; ++i){
docs[i] = "Renamed"+docs[i];
}
console.log("Renamed successfully");
return docs;
}
async function nextStage(docs){
// do some stuff and return something
// lets print the files simply
console.log(docs);
// and return anything if needed
return docs.length;
}
scan()
.then(inspect)
.then(nextStage)
.catch((error)=>{
// handle the error here
});
Just note how we are returning the Promise object from our very first function. We pass the data in resolve() and it will be injected as the argument in the next function mentioned in the .then()
call. Wherever you throw any error or call the reject() function, you will get to jump to the .catch()
call. Note that each chained call is returning a Promise object so that you can chain it further (run it in debugger to see yourself).