18

I'd like to have a function like this:

export async function* iterateDir(dir: string) {
    let list = await fs.readdir(dir); // fs-promise implementation of readdir
    for (let file of list) {
        yield file;
    }
}

Which I would use like:

for (let file in iterateDir(dir)) {
    processFile(file);
}

This doesn't work because a function cannot be both async and a generator.

How would I structure the code to achieve the same?

  1. If I change the await fs.readdir to callbacks, I assume the outer for..of loop would not wait.
  2. If I get rid of the generator and the directory is huge, iterateDir() will be slow.

For reference: async generator function proposal

7
  • If your async work is done before your generator work, then you can just make them different functions. Commented Nov 3, 2016 at 16:18
  • The point is that the generator itself needs to be async (it awaits filesystem operations). Commented Nov 3, 2016 at 16:21
  • Generators cannot be asynchronous (yet). The code you posted doesn't need to be an asynchronous generator; it can be split into an asynchronous part and a generator part. Commented Nov 3, 2016 at 16:31
  • 1
    Well, in my specific case, iterateDir would be recursive. For every file, there would be a detection whether it's a directory or not, and if it is, call iterateDir recursively. In which case I don't know how to split the async and generation functionality. Which is the point of the question.. Commented Nov 3, 2016 at 16:41
  • 1
    In that case, I recommend using observables (Rx.JS). Commented Nov 3, 2016 at 20:57

2 Answers 2

38

This is supported in TypeScript 2.3 - tracked issue

It introduces a few new types, notably:

interface AsyncIterable<T> {
    [Symbol.asyncIterator](): AsyncIterator<T>;
}

but most importantly it also introduces for await ... of

for await (const line of readLines(filePath)) {
  console.log(line);
}

where

async function* readLines(path) {
   //await and yield ...
}

Be aware that if you want to try this you will need to configure typescript to let it know you have run-time support (add "esnext.asynciterable" to lib list) you will probably need to polyfill Symbol.asyncIterator. see TS2318: Cannot find global type 'AsyncIterableIterator' - async generator

Sign up to request clarification or add additional context in comments.

Comments

-3

Stream of future values is called Observable. So the most natural thing is to use a library like RxJS or most.js. If you don't want to introduce a new complex library only to use it once, use the good old approach with callbacks:

const path = require("path");
const fs = require("mz/fs");

async function listDirectory(dirName, cb, level = 0) {
  for (let fileName of await fs.readdir(dirName)) {
    let absName = path.resolve(dirName, fileName);
    let isDirectory = (await fs.stat(absName)).isDirectory();
    cb({ level, fileName });
    if (isDirectory) {
      await listDirectory(absName, cb, level + 1);
    }
  }
}

listDirectory(".", ({ level, fileName }) => {
  console.log(" ".repeat(level) + fileName);
});

If you try to convert the callback to some better abstraction, you will rediscover RxJS sooner or later. It's similar to converting one-shot callbacks to promises.

2 Comments

The difference is that iterables are "pull" (caller requests next value) while Observables are "push" (called function determines when a new value is available and notifies the caller). In the OP's example, they really want a Promise (push) that resolves with a generator function (pull).
I was actually trying to use Observable and failed to write code that would be sensibly simple. The code I got was absurdly complex and I gave up on Observables. Then I was trying to find code examples and found none pretty much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.