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

May yield* send the last yield value? #3

Open
hax opened this issue Jun 6, 2016 · 7 comments
Open

May yield* send the last yield value? #3

hax opened this issue Jun 6, 2016 · 7 comments

Comments

@hax
Copy link
Member

hax commented Jun 6, 2016

YieldExpression:yield*AssignmentExpression
...
4. Let received be NormalCompletion(undefined).

May it update to 4. Let received be NormalCompletion(LastYieldValue) so we can satisfy:

function *g1() {
  yield function.sent
}
function *g2() {
  yield* g1()
}

g2().next(42) // => {value:42}

But allow delegate generator access the last yield value also have dark side:

function* service() {
  const token = function.sent
  if (auth(token)) {
    yield* serviceOf3rdParty()
  }
}

const serv = service()
serv.next('my-secret-token')
@Jamesernator
Copy link

I'd rather not see that happen by default given the second case, perhaps a variant of yield* or a function that forwards a value e.g.:

function* g2() {
    yield** g1()
}

// Or just a function (which can even be written in userland)

function* g2() {
    yield* withInitial(function.sent, g1())
}

@ljharb ljharb transferred this issue from allenwb/ESideas Aug 19, 2019
@hax
Copy link
Member Author

hax commented May 8, 2020

Consider the common use cases (if i can deal with the value of function.sent, process it in current generator, or delegate to sub generator), I think we need a ergonomic way to pass the value to sub generators. Besides new syntax like yield **g(), another possibility is changing function.sent to a function so we can have rich semantic :

function *g2() {
  // function.sent() seems not a proper name, so let's use function.readValue()
  const x = function.readValue() 
  if (i_can_deal_with_it()) {
    ...
  } else { // delegate to sub generator
    yield *g1() // g1 can read x via function.readValue()
  }
}

For the case of previous dark side example,

function* service() {
  // function.readValue(true) means the value is consumed
  // calling function.readValue() again before next next(v) would throw
  const token = function.readValue(true) 
  if (auth(token)) {
    yield* serviceOf3rdParty() // so serviceOf3rdParty can't see token
  }
}

const serv = service()
serv.next('my-secret-token')

@ljharb
Copy link
Member

ljharb commented May 8, 2020

It makes no sense to be to have it be "consumeable". It's a single static value, like a const; you can access it as many times as you need. yield * delegates control to an iterator, not a generator, so i don't think it's sensible or possible to pass it along.

@hax
Copy link
Member Author

hax commented May 8, 2020

It makes no sense to be to have it be "consumeable".

I'm not sure whether "consumable" is good or bad, but it's really consuming values from next(value) in function.sent usage.

It's a single static value, like a const;

It's not like const, every time after yield the value would change.

you can access it as many times as you need.

This is just current semantic, but I think it's not a hard requirement. As I understand, the only core requirement uptonow is providing a way to get the first value sent by next().

yield * delegates control to an iterator, not a generator, so i don't think it's sensible or possible to pass it along.

This issue is really about that. yield * g() will also delegate next(value) except the first call. Actually other proposals like iterator helpers also keep delegate next(value). So it 's of coz "sensible" or "possible" to pass it along, for example via userland code like yield* withInitial(function.sent, g1()) though it's not very ergonomic.

@hax
Copy link
Member Author

hax commented May 8, 2020

The essential problem of this issue is, currently yield* g will always start g with g.next(undefined), changing that to g.next(function.sent) (if keep the syntax untouched) is theoretically a breaking change, for example, code like below will break.

function iter() {
  return {
    [Symbol.iterator]() { return this },
    next(v) {
      // assume client code never send undefined after first call
      if (v === undefined) { // first call, 
        // do some stuff for setup
      } else {
        // process
      }
    }
  }
}

@ljharb
Copy link
Member

ljharb commented May 8, 2020

It's not like const, every time after yield the value would change.

you're right, my mistake - it's more like a live binding, as if from an import.

theoretically a breaking change

That's true, but if you design an iterator like that, anyone could break it very easily (including, with yield undefined) so i don't think that's a reasonable thing for us to be concerned about.

@hax
Copy link
Member Author

hax commented May 8, 2020

so i don't think that's a reasonable thing for us to be concerned about.

I'm not sure about that. If a big site had such code (of coz I hope it would never happen), land such change would "break the web" anyway. 😅

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

3 participants