question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

IteratorResult<T> definition conflicts with the JS spec

See original GitHub issue

TypeScript Version: 3.8.3

Search Terms: iterator return type

Code

const list = ['v1', 'v2', 'v3']

const f: () => Iterator<string> = () => {
  let index = 0
  return {
    next: (): IteratorResult<string> => {
      console.log(list)
      if (index < list.length) {
        return { value: list[index++]}
      } else {
        return { done: true }
      }
    }
  }
}

Expected behavior: The code should compile without errors. Actual behavior: The code compiles with the error: “Type ‘{ done: true; }’ is not assignable to type ‘IteratorResult<string, any>’. Property ‘value’ is missing in type ‘{ done: true; }’ but required in type ‘IteratorReturnResult<any>’.(2322)”

The error can be bypassed by changing { done: true } to { value: undefined, done: true }. However, the specification allows omitting the value property. See Iteration Protocols

A suggested fix would be to change the definition of the type IteratorReturnResult to:

interface IteratorReturnResult<TReturn> {
    done: true;
    value?: TReturn;
}

Playground Link: https://www.typescriptlang.org/play?#code/MYewdgzgLgBANgS2jAvDA2gcgG4EZMA0MOATIcdgMyYC6AUHaJLAGYBcMAFAJSoB8MAJJQApgCcAhlBBiAPNDEIwAcwFoe-GAG86MeCNhKAJiIAeqGAAZdMMQYCuYsNpt6wZqBx4dh4qTIAlEQh7OCh5KEUVNQEdPXiYJggQOBEAOjgQZU5EaG5XeIQWLmMzGFl4JCgMkRUoAAteOIT4uyhHZy0YbAk4exEOXKh0UtMAajGaAF8CvSmYETgIERcW1ocnbRgjcAGYSP6YGbXj+NOZqaA

Related Issues: #29982

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
zaachcommented, Sep 21, 2022

fwiw I ran into this in practice when attempting to use a ReadableStream as an async interator. E.g. this example from Jake Archibald’s article doesn’t type correctly:

function streamAsyncIterator(stream) {
  // Get a lock on the stream:
  const reader = stream.getReader();

  return {
    next() {
      // Stream reads already resolve with {done, value}, so
      // we can just call read:
      return reader.read();
    },
    return() {
      // Release the lock if the iterator terminates.
      reader.releaseLock();
      return {};
    },
    // for-await calls this on whatever it's passed, so
    // iterators tend to return themselves.
    [Symbol.asyncIterator]() {
      return this;
    }
  };
}
0reactions
rbucktoncommented, Sep 28, 2020

To reiterate, the problem is that, as written, IteratorReturnResult conflicts with the Iterator spec. @fatcerberus and I have suggested fixes. The argument against fixing it, so far, seems to be that it would make it less convenient to use, by requiring an undefined check. Two points:

  1. The definition of the Iterator spec is what’s driving the need for an ‘undefined’ check. I understand that one may wish it didn’t, but that’s a complaint about the spec itself. It doesn’t work to have a type that reflects how we wish the spec to be and not how it actually is.

I agree that value should be allowed to be removed when done is true, but only when the iterator is typed as IteratorResult<T, void>, which is why I’m investigating treating a property of type void as optional. However if your result is typed IteratorResult<T, number>, then the only correct type for the return result is { done: true, value: number }. Making value optional for that case would remove type safety. However, if we allow properties of type void to be optional as we do for parameters, then { done: true } would be assignable to { done: true, value: void }.

  1. The inconvenience of checking for undefined seems extremely minor, especially considering the optional chaining and non-null assertion operators. Moreover, and this bears repeating, the Iterator spec is defined in a way that makes the ‘undefined’ check unavoidable.

We made it such that calling generator.return() also requires a value if your generator is typed with a non-void return value, which ensures type safety. The only way I can see to accurately address this issue is to allow void properties to be considered optional.

Read more comments on GitHub >

github_iconTop Results From Across the Web

ECMAScript® 2023 Language Specification - TC39
Introduction. This Ecma Standard defines the ECMAScript 2023 Language. It is the fourteenth edition of the ECMAScript Language Specification.
Read more >
You Don't Know JS: ES6 & Beyond - GitHub Pages
IteratorResult value {property}: current iteration value or final return value ... is defined as sending a signal to an iterator that the consuming...
Read more >
function* - JavaScript - MDN Web Docs - Mozilla
Generators are functions that can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.
Read more >
ECMAScript® 2022 Language Specification - Ecma International
4.4 Terms and Definitions. 4.5 Organization of This Specification. 5 Notational Conventions. 5.1 Syntactic and Lexical Grammars. 5.2 Algorithm Conventions.
Read more >
javascript - js - avoiding namespace conflict - Stack Overflow
Properties of the object are the modules I defined before. // file BI.lib.js var lzheA = { foo: function() { } ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found