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 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 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), []));
      });
    });
  });
}
@jordan314

This comment has been minimized.

Copy link

jordan314 commented Sep 13, 2018

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

@JustMaier

This comment has been minimized.

Copy link

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 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 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 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 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 commented Jun 6, 2020

Ah yes, very good point.

@clubside

This comment has been minimized.

Copy link

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 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 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 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 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
You can’t perform that action at this time.