TypeScript Version: 3.6.2
Search Terms:
async generator, AsyncGenerator, optional .next
Code
The following causes a type error:
async function* sequence(iterable: AsyncIterable<number>): AsyncGenerator<number> {
yield 12;
try {
// Type error: Cannot delegate iteration to value because the 'next' method of its iterator expects type 'undefined', but the containing generator will always send 'unknown'.
yield* iterable;
} finally {
console.log('Cleanup!');
}
}
Expected behavior:
I would've expected it to be a non-type error.
Problem:
The primary annoyance with this is that I'd like to use AsyncGenerator<T> just for specifying that .return() can be used without non-null assertions (.return!()) for early cleanup but AsyncGenerator<T> results in AsyncGenerator<T, any, unknown> so AsyncIterable<T> can't be delegated to.
Proposed solution:
Change interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> to interface AsyncGenerator<T = unknown, TReturn = any, TNext = any.
This won't break anything as any is assignable to anything, and I doubt it'll be problematic as those using AsyncGenerator<T> (or AsyncGenerator<T, S>) presumably do not care about the .next parameter.
The problem with any as a default is that it allows you to violate the TNext constraint of the thing you are delegating to with yield*:
async function * g(): AsyncGenerator<number, void, string> {
const x = yield;
x.toUpperCase(); // because `x` *must* be a string
}
async function * f(other: AsyncGenerator<number, void, string>): AsyncGenerator<number> {
yield* other; // no error because `any` is assignable to `string`
}
// compile time: no error because `1` is assignable to `any`
// runtime: errors because `toUpperCase` is not defined on a number
f(g()).next(1);
Perhaps the default for TNext in AsyncIterator and Iterator should be unknown instead of undefined. I will need to run some tests to verify that this won't have other side effects.
Most helpful comment
The problem with
anyas a default is that it allows you to violate theTNextconstraint of the thing you are delegating to withyield*:Perhaps the default for
TNextinAsyncIteratorandIteratorshould beunknowninstead ofundefined. I will need to run some tests to verify that this won't have other side effects.