Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generators: what are they good for? #8

Open
jorendorff opened this issue Dec 20, 2019 · 7 comments
Open

Generators: what are they good for? #8

jorendorff opened this issue Dec 20, 2019 · 7 comments
Assignees

Comments

@jorendorff
Copy link

The rationale for a proposal should do two things:

  • illustrate what the feature is for and what it's not for
  • make the case that that's worthy of language-level support

The adder example doesn't really advance either of these. So let's talk a little about why we're doing this, and see if we can come up with examples that really motivate the feature.

@jorendorff
Copy link
Author

(background: hi, my name is @jorendorff and my role in TC39 is to complain that the proposal README doesn't have good examples)

@ljharb
Copy link
Member

ljharb commented Dec 20, 2019

There's a value that a user can provide to a generator's iterator (and may assume will mean something) that the generator function can't possibly detect or receive - function.sent provides that value. I think the adder example, while contrived, shows how someone would intuitively use an iterator in a way that wouldn't be compatible with using a generator function to produce it.

@jorendorff
Copy link
Author

Right. A less contrived example would be an improvement, but that's not the main issue.

I'm after evidence that sending values to generators has proven valuable enough to justify further investment.

My experience is that generators are fantastic for implementing iterators—they're like magic—but are otherwise brittle and inconvenient. Sometimes I want to use them as a general tool for inversion of control, but it doesn't work out. I usually (maybe always) end up needing a total rewrite, and usually very soon, before I even land a patch. Maybe it's just me; hence the question: Is the rest of the JS world doing a bunch of work with generators that this feature would facilitate? Either answer would help; certainly the proposal could be viable either way.

@jorendorff
Copy link
Author

I guess with iterators I really feel like we're on solid ground, if only because I use them myself all the time (not really joking, unfortunately), so the Iterator methods proposal is an easy +1.

With this, it feels like we must be talking about supporting some other way of programming that I've never seen, and have actually tried and failed to make it work out. So what is that exactly? Let's put a little more of that in the README.

@lifaon74
Copy link

Here some practical example if it may help: Writable streams.

(written in Typescript for types)

import { promises as $fs } from 'fs';

async function * writeStream(path: string): AsyncGenerator<number, void, Uint8Array| null> {
  const fd: $fs.FileHandle = await $fs.open(path, 'w', 0o777);
  let position: number = 0;
  try {
    while (true) {
      const buffer: Uint8Array | null = yield position;
      if (buffer === null) {
        return;
      } else {
        if (buffer.length > 0) {
          const { bytesWritten } = await fd.write(buffer, 0, buffer.length, position);
          position += bytesWritten;
        }
      }
    }
  } finally {
    await fd.close();
  }
}

const stream = writeStream('file.txt');
stream.next(); // sadly we can't pass immediately a value (=> where this spec aims to find a workaround)
stream.next(new Uint8Array([1, 2, 3]));
stream.next(new Uint8Array([4, 5, 6]));
stream.next(null); // ends the stream

The function creates a "stream" based on an AsyncGenerator, to write data in a file.

INFO: I know that Streams already exists: NodeJS and StreamAPI. This is just an example, but still, creating "streams" from AsyncGenerator is sometimes pretty convenient and useful.

@nikolaybotev
Copy link

nikolaybotev commented Sep 10, 2024

FYI, the current implementation of yield * also exhibits the same kind of awkward behavior of not processing the first value received from next():

yield* calls the iterator's next() method, passing the argument received by the generator's next() method (always undefined for the first call)

> var x = (function* () { yield* new class { next = (value) => ({ value }); [Symbol.iterator] = () => this } })()
undefined
> x.next(24)
{ value: undefined }
> x.next(24)
{ value: 24 }

@hax
Copy link
Member

hax commented Sep 13, 2024

@nikolaybotev Yes, see the old issue: #3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants