I have the following generator function:
async function * filterIterable (iter, predicate) {
let i = 0
for await (const val of iter) {
if (predicate(val, i++)) {
yield val
}
}
}
that I would like to type for the following two cases:
// type predicate
const nonNullable = <T>(val: T | undefined | null): val is T =>
val !== undefined && val !== null
async function * gen1 () {
yield 1
yield 2
yield 3
}
async function * gen2 () {
yield 1
yield undefined
yield 3
}
const it1 = filterIterable(gen1(), n => n % 2 === 0)
const it2 = filterIterable(gen2(), nonNullable) // should be AsyncIterable<1 | 2>
I came up with this interface:
interface FilterIterable {
<T> (
iter: AsyncIterable<T>,
predicate: (val: T, index: number) => boolean,
): AsyncIterable<T>;
<T, S extends T> (
iter: AsyncIterable<T>,
predicate: (val: T, index: number) => val is S,
): AsyncIterable<S>;
}
which I could apply to a function expression, but apparently not to a function declaration per How to apply a function type to a function declaration. Is this not what the generator is?
So you want filterIterable()
to be an overloaded function with multiple call signatures. While it's true that you can't currently directly annotate a function as requested in microsoft/TypeScript#22063, you could just use regular overload syntax and declare the call signatures before the implementation:
// call signatures
function filterIterable<T, S extends T>(
iter: AsyncIterable<T>, predicate: (val: T, index: number) => val is S
): AsyncIterable<S>;
function filterIterable<T>(
iter: AsyncIterable<T>, predicate: (val: T, index: number) => boolean
): AsyncIterable<T>;
// implementation
async function* filterIterable<T>(
iter: AsyncIterable<T>, predicate: (val: T, index: number) => boolean
) {
let i = 0
for await (const val of iter) {
if (predicate(val, i++)) {
yield val
}
}
}
This should now work as you intended:
async function foo() {
const it1 = filterIterable(gen1(), n => n % 2 === 0)
for await (const x of it1) {
// ^? const x: 1 | 2 | 3
console.log(x.toFixed(2))
}
const it2 = filterIterable(gen2(), nonNullable)
for await (const y of it2) {
// ^? const y: 1 | 3
console.log(y.toFixed(3))
}
}