# Generator function to enumerate files

I want to implement a function that enumerates all files in directory. I think the function should

• Return a Promise, because underlying readdir call is prefered over readdirSync.
• Be a generator function, because checking statSync(...).isFile() might take some time and maybe, I will need only first few files...

So my attempt

import * as fs from 'fs';
import * as path from 'path';

function enumerateFiles(dir: string): Promise<IterableIterator<string>> {
let result = function* (files: string[]): IterableIterator<string> {
for (let enty of files) {
let fullpath = path.join(dir, enty);

if (fs.statSync(fullpath).isFile())
yield fullpath;
}
};

return new Promise<IterableIterator<string>>((resolve, reject) => {
if (err)
reject(err);
else
resolve(result(files));
});
});
}


And usage

for (let file of await enumerateFiles(String.rawC:\Windows\Web\Screen))
console.log(file);

// C:\Windows\Web\Screen\img100.jpg
// C:\Windows\Web\Screen\img101.jpg
// ...


What do you think? I'm really not sure about combining Promises with generator functions. Especially in this order...

Right now the biggest problem with this function that I see is the mixing of async and sync fs APIs. This is confusing to the consumer - if I have to deal with promises, then chances are I will expect you are doing everything asynchronously behind the scenes.

I think this function is a good idea, but it would be better if you took advantage of the Async iterator proposal, which Typescript can transpile.

import * as fs from 'fs'
import { join } from 'path'
import { promisify } from 'util'

const statPromise = promisify(fs.stat)

const isFile = (path: string) => statPromise(path)
.then(stats => stats.isFile())

export async function * enumerateFiles (path: string): AsyncIterableIterator<string> {
for (const file of files) {
if (await isFile(join(path, file)) {
yield file
}
}
}


And here's an example of usage:

// Async function is required.
async function main () {
for await (const file of enumerateFiles('.')) {
console.log(file)
}
}

main()


Note that to use async iterators, you need to add esnext.asynciterable to the lib section of your tsconfig.json.

• Sorry for the delay in posting this - I was pretty sure I knew the async iterator proposal was the way to do this more cleanly, but didn't have the time to look into it until today. – Gerrit0 Feb 14 '18 at 23:43
• Hey, this is really cool solution! Thanks for both, notifying me about this proposal and demonstrating it in my problem. I’m starting using this pattern from now :-). Again, thanks a lot! – Matěj Pokorný Feb 15 '18 at 23:20
for (let file of await enumerateFiles(String.rawC:\Windows\Web\Screen))


The await looks like a leaky abstraction. Could your generator naturally behave synchronously? A quote from Exploring ES6

22.1.5 Use case: receiving asynchronous data

Generators can receive input from next() via yield. That means that you can wake up a generator whenever new data arrives asynchronously and to the generator it feels like it receives the data synchronously.