You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Trouble implementing AsyncIterator interface in 3.6.2 #33239
classConstantIterator<T>implementsAsyncIterator<T,undefined,T|undefined>{constructor(privateconstant: T){}asyncnext(value?: T): Promise<IteratorResult<T>>{if(value!=null){thrownewError("ConstantIterator.prototype.next may not take any values");}return{value: this.constant,done: false};}}
Expected behavior:
Code compiles. Actual behavior:
constant.ts(4,9): error TS2416: Property'next'intype 'ConstantIterator<T>' is not assignable to the same property in base type 'AsyncIterator<T,undefined,T>'.Type '(value?: T)=>Promise<{value: T; done: boolean;}>' is not assignable to type '(...args: []|[T|PromiseLike<T>])=>Promise<IteratorResult<T,undefined>>'.Typesofparameters 'value' and'args'areincompatible.Type'[] | [T | PromiseLike<T>]'isnotassignabletotype'[T?]'.Type'[T | PromiseLike<T>]'isnotassignabletotype'[T?]'.Typesofproperty'0'areincompatible.Type 'T|PromiseLike<T>' is not assignable to type 'T'. 'T|PromiseLike<T>' is assignable to the constraint of type 'T',but'T'couldbeinstantiatedwithadifferentsubtypeofconstraint'{}'.Type 'PromiseLike<T>' is not assignable to type 'T'. 'PromiseLike<T>' is assignable to the constraint of type 'T',but'T'couldbeinstantiatedwithadifferentsubtypeofconstraint'{}'.
Note that the synchronous version of ConstantIterator does not throw the same error. It’s the type union of T | PromiseLike<T> which seems to cause the error.
Playground Link:
Playground has yet to be updated for 3.6.
Related Issues: #30790 Strongly typed iterator PR. #32682 Other (async) iterator usability issues. #33131 Possible change to type inference in 3.6.
Gonçalo Fernandes (@ilogico) Woops, I forgot to change the return type when I was testing that this error does not happen for sync iterators.
classConstantIterator<T>implementsAsyncIterator<T,undefined,T|undefined>{constructor(privateconstant: T){}asyncnext(value?: T): Promise<IteratorResult<T>>{if(value!=null){thrownewError("ConstantIterator.prototype.next may not take any values");}return{value: this.constant,done: false};}}
try running tsc version 3.6.2 on the above code.
the next method must accept not only T | undefined, but also PromiseLike<T | undefined>
Is this an actual requirement in the async iterator spec? Can you point to where this is stated? As far as I can tell I have never defined an async iterator which expected a promise as the result of a yield.
Yeah I initially thought it was an assignability error related to the weird tuple args, but I think it’s probably the fact that TNext should never be unwrapped as a Promise. Likely just a misunderstanding of async iterators?
Nathan Shively-Sanders (@sandersn) Can you explain the addition of the “Working as Intended” label? Here’s the current AsyncIterator interface defined by typescript:
interfaceAsyncIterator<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|PromiseLike<TNext>]): Promise<IteratorResult<T,TReturn>>;return?(value?: TReturn|PromiseLike<TReturn>): Promise<IteratorResult<T,TReturn>>;throw?(e?: any): Promise<IteratorResult<T,TReturn>>;}
Requiring the next method to have a first argument of type TNext | PromiseLike<TNext> implies that all well-formed async iterators unwrap the value passed to next, and this is a requirement which even async generators do not adhere to. It is strictly incorrect and I can point to the exact parts of the spec which prove this.
NOTE 1
Arguments may be passed to the next function but their interpretation and validity is dependent upon the target AsyncIterator. The for-await-of statement and other common users of AsyncIterators do not pass any arguments, so AsyncIterator objects that expect to be used in such a manner must be prepared to deal with being called with no arguments.
25.5.3.5 AsyncGeneratorResumeNext
These are all the definitions which relate to the TNext type parameter as it relates to async generators. The method AsyncGenerator.prototype.next calls AsyncGeneratorEnqueue with TNext as a normal completion, AsyncGeneratorEnqueue adds the completion to a queue without reading or awaiting it, and AsyncGeneratorResumeNext pulls the completion from the queue, and if the completion is not abrupt (which it won’t be because a next call never enqueues an abrupt completion), it resumes the generator with the completion as the value of yield. See step 18 of AsyncGeneratorResumeNext:
Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
At no point in any of these steps is the TNext value awaited, and this is proven to be true in the comment above.
The correct definition of AsyncIterator should probably be:
interfaceAsyncIterator<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): Promise<IteratorResult<T,TReturn>>;throw?(e?: any): Promise<IteratorResult<T,TReturn>>;}
In other words, the same as Iterator except the return types are all promises.
To be 100% clear: if a Promise<string> is passed to next, then inferring TNext = string is wrong; the correct inference is Promise<string>, because that’s what will be observed by the generator.
Would it be possible to fix this in a 3.6 (3.6.4?) release? Currently assigning/implementing async iterators is really broken on 3.6 and I don’t like idea of telling people you have to skip a version of typescript just to use them.
I would also be willing to volunteer time to get this and other (async) iterator issues fixed! Let me know if you’re willing to accept non-microsoft contributions for these issues.
Activity
ilogico commentedon Sep 5, 2019
The error message has a lot of text, but it's clear:
Promise<IteratorResult<T>>and notIteratorResult<T>T | undefined, but alsoPromiseLike<T | undefined>brainkim commentedon Sep 5, 2019
Gonçalo Fernandes (@ilogico) Woops, I forgot to change the return type when I was testing that this error does not happen for sync iterators.
try running tsc version 3.6.2 on the above code.
Is this an actual requirement in the async iterator spec? Can you point to where this is stated? As far as I can tell I have never defined an async iterator which expected a promise as the result of a yield.
fatcerberus commentedon Sep 5, 2019
Based on a quick test in Node.js, there is no special handling for passing promises to
.next():The promise is not unwrapped by
yield(unless explicitlyawaited).brainkim commentedon Sep 5, 2019
Yeah I initially thought it was an assignability error related to the weird tuple args, but I think it’s probably the fact that
TNextshould never be unwrapped as a Promise. Likely just a misunderstanding of async iterators?brainkim commentedon Sep 6, 2019
Nathan Shively-Sanders (@sandersn) Can you explain the addition of the “Working as Intended” label? Here’s the current
AsyncIteratorinterface defined by typescript:Requiring the
nextmethod to have a first argument of typeTNext | PromiseLike<TNext>implies that all well-formed async iterators unwrap the value passed tonext, and this is a requirement which even async generators do not adhere to. It is strictly incorrect and I can point to the exact parts of the spec which prove this.The note following the definition states (emphasis mine):
These are all the definitions which relate to the
TNexttype parameter as it relates to async generators. The methodAsyncGenerator.prototype.nextcallsAsyncGeneratorEnqueuewithTNextas a normal completion,AsyncGeneratorEnqueueadds the completion to a queue without reading or awaiting it, andAsyncGeneratorResumeNextpulls the completion from the queue, and if the completion is not abrupt (which it won’t be because anextcall never enqueues an abrupt completion), it resumes the generator with the completion as the value of yield. See step 18 ofAsyncGeneratorResumeNext:At no point in any of these steps is the
TNextvalue awaited, and this is proven to be true in the comment above.The correct definition of
AsyncIteratorshould probably be:In other words, the same as
Iteratorexcept the return types are all promises.You could make an argument that the value passed to
returnshould beTReturn | PromiseLike<TReturn>based on step 10 ofAsyncGeneratorResumeNextand 25.5.3.5.1 AsyncGeneratorResumeNext Return Processor Fulfilled Functions which does state that promises passed toAsyncGenerator.prototype.returnare unwrapped, but theAsyncIteratorinterface itself as defined by the spec does not contain any directives that all async iterators should unwrap the value passed toreturn.In any case, the value passed to
nextis not required to be unwrapped anywhere in the spec for both async iterators and async generators.ilogico commentedon Sep 6, 2019
I agree that requiring the next method to accept
PromiseLike<TNext>is incorrect.If some iterators allow it, they should declare it explicitly.
fatcerberus commentedon Sep 6, 2019
To be 100% clear: if a
Promise<string>is passed tonext, then inferringTNext = stringis wrong; the correct inference isPromise<string>, because that’s what will be observed by the generator.sandersn commentedon Sep 6, 2019
Brian Kim (@brainkim) I understood your comment
To refer to your misunderstanding. But it sounds like it's a misunderstanding by the author of lib.d.ts?
brainkim commentedon Sep 6, 2019
Nathan Shively-Sanders (@sandersn) Misunderstandings all around! Yes, I think lib.d.ts was written with a slight misunderstanding of async iterators.
brainkim commentedon Sep 10, 2019
Would it be possible to fix this in a 3.6 (3.6.4?) release? Currently assigning/implementing async iterators is really broken on 3.6 and I don’t like idea of telling people you have to skip a version of typescript just to use them.
I would also be willing to volunteer time to get this and other (async) iterator issues fixed! Let me know if you’re willing to accept non-microsoft contributions for these issues.
rbuckton commentedon Sep 10, 2019
You are correct that
nextandreturndo not await the values passes back to the async generator. I am looking into fixing the types now.