0

I'm writing an recursive function, which creates an object tree of selected file directory. My code works, but in the wrong order that I expected. I can't see the output of my code. Here is the code:

const fs = require("fs");

const basePath = process.argv[2];
const result = {};

const isDirectory = path => {
  return new Promise((resolve, reject) => {
    fs.lstat(path, (err, stats) => {
      if (err) reject("No such file or Directory");

      resolve(stats.isDirectory());
    });
  });
};

const createTree = (path, target) => {
  return new Promise((reject, resolve) => {
    fs.readdir(path, (err, list) => {
      for (const item of list) {
        const currentLocation = `${path}/${item}`;
        isDirectory(currentLocation).then(isDir => {
          console.log(result); //I CAN SEE THE RESULT HERE
          if (!isDir) {
            target[item] = true;
          } else {
            target[item] = {};
            resolve(createTree(currentLocation, target[item]));
          }
        });
      }
    });
    reject("Somthing went wrong while getting the list of files");
  });
};

createTree(basePath, result)
  .then(() => console.log("result --->", result)) //BUT NOT HERE
  .catch(err => console.log("Consume Error ==>", err));

I also done it with async await, but I'm curious why it doesn't work with promises.

Here is the fully working exaple with async await:

const fs = require("fs");

const basePath = process.argv[2]; //Getting the path
const result = {};

//Function to check my path is exist and it's a directory
const isDirectory = async path => {
  try {
    const stats = await fs.promises.lstat(path); //Used istat to get access to the "isDirectory()" method
    return stats.isDirectory();
  } catch (error) {
    throw new Error("No such file or Directory");
  }
};

//Recursive function that should create the object tree of the file system
const createTree = async (path, target) => {
  try {
    const list = await fs.promises.readdir(path);
    for (const item of list) {
      const currentLocation = `${path}/${item}`;
      const isDir = await isDirectory(currentLocation);
      //If it's a file, then assign it to true
      //Otherwise create object of that directory and do the same for it
      if (!isDir) {
        target[item] = true;
      } else {
        target[item] = {};
        await createTree(currentLocation, target[item]);
      }
    }
  } catch (err) {
    console.log("Somthing went wrong while getting the list of files");
  }
};

//Consuming the createTree function
(async () => {
  try {
    await createTree(basePath, result);
    console.log(result);
  } catch (error) {
    console.log(error.message);
  }
})();
  • 3
    I don't see you calling resolve in the first version, in createTree. You need to resolve the promise somewhere to make it work. With async/await of course "it is done automatically" – godzsa Sep 28 '20 at 11:17
  • 3
    In `createTree`, `new Promise((reject, resolve) => {` --> `new Promise((resolve, reject) => {`, plus you don't call `resolve` anyway. – sp00m Sep 28 '20 at 11:19
  • You should really check out `const fs = require('fs/promises');` – Adam Jenkins Sep 28 '20 at 11:29
  • @sp00m I tried to call resolve and put into it the recursive call, it returned the same as without resolve, in other places that I tried it just rejects. Can you help me with that please? I appreciate you attention)) – Mike Ehrmantraut Sep 28 '20 at 12:38
  • You cannot `resolve` a Promise multiple times. This is what you `for` loop is trying to do. Is there a reason you duplicated your [previous question](https://stackoverflow.com/questions/64090125/create-object-tree-from-file-system)? What was wrong with the answers provided there? – Mulan Sep 28 '20 at 19:47
  • @Thankyou I'm just curious is it possible to do the same but only with promises. – Mike Ehrmantraut Sep 29 '20 at 09:44

1 Answers1

2

I'm just curious is it possible to do the same but only with promises.

async and await are simply syntactic sugar that makes it easier to work with Promise-based programs. Any program depending on those keywords can be rewritten not to use them -

// main.js

import { readdir } from "fs/promises"
import { join } from "path"

function createTree (init = ".")
{ const one = path => p =>
    p.isDirectory()
      ? many(join(path, p.name)).then(r => ({ [p.name]:  r }))
      : { [p.name]: true }

  const many = path =>
    readdir(path, { withFileTypes: true })
      .then(r => Promise.all(r.map(one(path))))
      .then(r => Object.assign(...r))

  return many(init)
}

createTree(".")
  .then(v => console.log(JSON.stringify(v, null, 2)))
  .catch(console.error)

Now let's add some sample files so we can see our program working correctly -

$ yard add immutable    # (any example package)
$ node main.js

Output -

{
  "main.js": true,
  "node_modules": {
    ".yarn-integrity": true,
    "immutable": {
      "LICENSE": true,
      "README.md": true,
      "contrib": {
        "cursor": {
          "README.md": true,
          "__tests__": {
            "Cursor.ts.skip": true
          },
          "index.d.ts": true,
          "index.js": true
        }
      },
      "dist": {
        "immutable-nonambient.d.ts": true,
        "immutable.d.ts": true,
        "immutable.es.js": true,
        "immutable.js": true,
        "immutable.js.flow": true,
        "immutable.min.js": true
      },
      "package.json": true
    }
  },
  "package.json": true,
  "yarn.lock": true
}

If you would like the init path to be included in the tree, only a small modification is necessary -

// main.js

import { readdir } from "fs/promises"
import { join, basename } from "path" // !

function createTree (init = ".")
{ const one = path => p =>
    p.isDirectory()
      ? many(join(path, p.name)).then(r => ({ [p.name]:  r })) // !
      : { [p.name]: true }

  const many = path =>
    readdir(path, { withFileTypes: true })
      .then(r => Promise.all(r.map(one(path))))
      .then(r => Object.assign(...r)) // !
      .then(r => ({ [basename(path)]: Object.assign(...r) })) // !

  return many(init)
}

Now the tree contains our initial path -

createTree(".")
  .then(v => console.log(JSON.stringify(v, null, 2)))
  .catch(console.error)
{ ".":      // <- starting path 
  { ... }
}

To see how to write this program using async generators, please see the original Q&A.

Mulan
  • 129,518
  • 31
  • 228
  • 259