Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Walk through a directory recursively in node.js.
// ES6 version using asynchronous iterators, compatible with node v10.0+
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* walk(entry);
else if (d.isFile()) yield entry;
}
}
// Then, use it with a simple async for loop
async function main() {
for await (const p of walk('/tmp/'))
console.log(p)
}
// Callback-based version for old versions of Node
var fs = require("fs"),
path = require("path");
function walk(dir, callback) {
fs.readdir(dir, function(err, files) {
if (err) throw err;
files.forEach(function(file) {
var filepath = path.join(dir, file);
fs.stat(filepath, function(err,stats) {
if (stats.isDirectory()) {
walk(filepath, callback);
} else if (stats.isFile()) {
callback(filepath, stats);
}
});
});
});
}
if (exports) {
exports.walk = walk;
} else {
walk(".", manageFile);
}
@jimmylab

This comment has been minimized.

Copy link

@jimmylab jimmylab commented Dec 5, 2017

Here comes a problem: how can I know when would the traversal stop?

@catamphetamine

This comment has been minimized.

Copy link

@catamphetamine catamphetamine commented Jun 27, 2018

@jimmylab Use Promises

import fs from 'fs';
import path from 'path';


function walk(dir) {
  return new Promise((resolve, reject) => {
    fs.readdir(dir, (error, files) => {
      if (error) {
        return reject(error);
      }
      Promise.all(files.map((file) => {
        return new Promise((resolve, reject) => {
          const filepath = path.join(dir, file);
          fs.stat(filepath, (error, stats) => {
            if (error) {
              return reject(error);
            }
            if (stats.isDirectory()) {
              walk(filepath).then(resolve);
            } else if (stats.isFile()) {
              resolve(filepath);
            }
          });
        });
      }))
      .then((foldersContents) => {
        resolve(foldersContents.reduce((all, folderContents) => all.concat(folderContents), []));
      });
    });
  });
}
@alana314

This comment has been minimized.

Copy link

@alana314 alana314 commented Sep 13, 2018

This is better than npm's walk IMO. Thank you!

@JustMaier

This comment has been minimized.

Copy link

@JustMaier JustMaier commented May 27, 2019

Here's an es6 version

const fs = require('fs').promises;
const path = require('path');

async function walk(dir) {
    let files = await fs.readdir(dir);
    files = await Promise.all(files.map(async file => {
        const filePath = path.join(dir, file);
        const stats = await fs.stat(filePath);
        if (stats.isDirectory()) return walk(filePath);
        else if(stats.isFile()) return filePath;
    }));

    return files.reduce((all, folderContents) => all.concat(folderContents), []);
}
@Acidic9

This comment has been minimized.

Copy link

@Acidic9 Acidic9 commented May 11, 2020

Synchronous version:

const walkSync = (dir, callback) => {
  const files = fs.readdirSync(dir);
  files.forEach((file) => {
    var filepath = path.join(dir, file);
    const stats = fs.statSync(filepath);
    if (stats.isDirectory()) {
      walkSync(filepath, callback);
    } else if (stats.isFile()) {
      callback(filepath, stats);
    }
  });
};
@lovasoa

This comment has been minimized.

Copy link
Owner Author

@lovasoa lovasoa commented May 11, 2020

I think the best way to do this in modern javascript is with an asynchronous iterator :

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;
    }
}

The code is both clear, non-blocking, concise, easy to use, and avoids the explicit call to stats. It works in node v10.0+. You then iterate with just

for await (const p of walk('/tmp/'))
    console.log(p)
@npomfret

This comment has been minimized.

Copy link

@npomfret npomfret commented Jun 6, 2020

@lovasoa I agree. But a useful feature (for me anyway) is to give the callback the ability to stop the process (when the file has been found, or it has gone too deep for example)

@lovasoa

This comment has been minimized.

Copy link
Owner Author

@lovasoa lovasoa commented Jun 6, 2020

@npomfret : It is actually easier to stop the process early with the asynchronous iterator; just use break :

example :

  for await (const p of walk('/')) {
    if (p.endsWith("passwd")) {
      console.log(p)
      break;
    }
  }
@npomfret

This comment has been minimized.

Copy link

@npomfret npomfret commented Jun 6, 2020

Ah yes, very good point.

@clubside

This comment has been minimized.

Copy link

@clubside clubside commented Jun 13, 2020

I'm just getting started with node so I'm sure the answer is straight forward so forgive me...but if I enter the May 11 code first it won't accept the await part of for await (const p of walk('/tmp/')). If I remove that it will run then throw an exception on the walk part of that same line saying its not a function or its return value is no iterable. Too many concepts coming at me at once "p so I don't even know where to start in terms of figuring out these errors. Any help?

@lovasoa

This comment has been minimized.

Copy link
Owner Author

@lovasoa lovasoa commented Jun 13, 2020

@clubside

if I enter the May 11 code first

I'm not sure I understand that...

it won't accept the await part of for await (const p of walk('/tmp/'))

I guess you are either using an old version version of node or you are trying to use for await outside of an async function.

Here are a few articles you can read to get started :

@clubside

This comment has been minimized.

Copy link

@clubside clubside commented Jun 14, 2020

I'm using Node 12 according to node -v

It just looked like the two parts of the May 11 comment were a standalone I could just run but thanks for the links I'll see if I can work it out!

@jasonk

This comment has been minimized.

Copy link

@jasonk jasonk commented Jun 17, 2020

You can easily turn it into a standalone, just wrap the top-level code in an async function:

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 main() {
  for await (const p of walk('/tmp/'))
    console.log(p)
}
main()

Or do it all in one, like:

( async () => {
  for await (const p of walk('/tmp/'))
    console.log(p)
} )();
@cristea2017

This comment has been minimized.

Copy link

@cristea2017 cristea2017 commented Jul 6, 2020

Here's an es6 version

const fs = require('fs').promises;
const path = require('path');

async function walk(dir) {
    let files = await fs.readdir(dir);
    files = await Promise.all(files.map(async file => {
        const filePath = path.join(dir, file);
        const stats = await fs.stat(filePath);
        if (stats.isDirectory()) return walk(filePath);
        else if(stats.isFile()) return filePath;
    }));

    return files.reduce((all, folderContents) => all.concat(folderContents), []);
}

I searched for this a lot of time , Thanks ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment