javascripttypescriptasync-iterator

How is the return method used in iterators / async iterators?


MDN states that async iterators have a return method

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      next() {
        const done = i === LIMIT;
        const value = done ? undefined : i++;
        return Promise.resolve({ value, done });
      },
      return() {
        // This will be reached if the consumer called 'break' or 'return' early in the loop.
        return { done: true };
      }
    };
  }
};

However, the Typescript definitions of async iterators require the return method to

  1. accept an optional value
  2. return {value: someValue, done: true}, whereas MDN does not do this.

Here's the TS definition:

interface AsyncIterator<T, TReturn = any, TNext = undefined> {
    // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
    next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
    return?(value?: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
    throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
}

Can someone explain the discrepancy? How does return work?


Solution

  • MDN, however authoritative, is not the source of truth when it comes to language specifications. TypeScript follows the ECMAScript spec, in this instance, the definition of the AsyncIterator interface (as @@asyncIterator well-known symbol is a method that returns an AsyncIterator).

    Table 76 and 77 of the spec provide, respectively, the required and optional properties of the interface: next, return, and throw. We are interested in what the spec has to say about the parameters of the second one:

    The returned promise will fulfill with an IteratorResult object which will typically have a "done" property whose value is true, and a "value" property with the value passed as the argument of the return method. However, this requirement is not enforced.

    Note the last part of the first sentence — here is where the TReturn | PromiseLike<TReturn> comes from. Then note the second sentence — this is why the parameter is optional.

    As you can also see from the above, the return method returns an object implementing the IteratorResult interface which is defined to have a done boolean and a value unrestricted property. Both properties are not considered optional, but each has a note in table 78 of the spec that allows for either to be missing.

    However, TypeScript definition for the IteratorReturnResult (a member of the IteratorResult union type) does not take into account those notes and marks those properties as required:

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

    This has been called into question in issue #8938 on the source repository with the team reasoning being:

    As noted by @ivogabe we need to have boolean literal types, to be able to model this one accurately.

    and subsequently closed in favor of issue #2983 fixed by PR #30790 with the original request seemingly slipping through the cracks.