Reputation: 636
For example, when I try to do this:
function chain<A, B extends (input: A, loop: (input: A) => C) => any, C extends ReturnType<B>> (input: A, handler: B): C {
const loop = (input: A): C => handler(input, loop);
return loop(input);
}
chain('test', async (value, loop): Promise<string> => {
const ready = await someExternalThing();
if (ready) {
return value;
} else {
return await loop(value + '!');
}
});
Even with a strict return type defined for the handler function, the loop function's return type in the bottom code is still showing up as "any"
Does anybody know any way around this issue?
Upvotes: 1
Views: 88
Reputation: 22848
I wasn't able to derive the template parameters automatically from the return type of the handler. Still searching for a solution there.
But if you are willed to give the template parameters manually, there is a solution for you:
let i = 0;
async function someExternalThing() {
i += 1;
return i > 3;
}
async function chain<VAL, RET>(
input: VAL,
handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
const loop = (input: VAL): Promise<RET> => handler(input, loop);
return loop(input);
}
const result = chain<string, string>('test', async (value, loop) => {
const ready = await someExternalThing();
if (ready) {
return value;
} else {
return await loop(value + '!');
}
});
result.then((value) => {
console.log(`Result: ${value}`)
})
Result: test!!!
And not a single type in the script is any
or unknown
.
This one seems to work:
async function chain<VAL, RET>(
input: VAL,
handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
const loop = (input: VAL): Promise<RET> => handler(input, loop);
return loop(input);
}
const result = chain('test', async (value): Promise<string> => {
return value;
});
But as soon as you introduce the loop
parameter, RET
goes back to unknown
. So I feel like it's a limitation of the typescript type inferring mechanism at the moment. I think it might work in future.
async function chain<VAL, RET>(
input: VAL,
handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
const loop = (input: VAL): Promise<RET> => handler(input, loop);
return loop(input);
}
const result = chain('test', async (value, loop): Promise<string> => {
return value;
});
Upvotes: 1
Reputation: 636
Based on the solutions of Gael J and Finomnis, I decided to make a version that only requires specifying one type parameter, rather than two:
class Chainer<A> {
run <B>(input: B, handler: (input: B, loop: (input: B) => A) => A) {
const loop = (input: B) => handler(input, loop);
return loop(input);
};
}
new Chainer<Promise<string>>().run('test', async (value, loop) => {
const ready = await someExternalThing();
if (ready) {
return value;
} else {
return await loop(value + '!');
}
});
It uses a class as a sort of holder for the handler return type, then uses the run
method to inference the "input" type from the first argument.
Another Method
Using a wrapper function instead of a class:
function chain<A> () {
return <B>(input: B, handler: (input: B, loop: (input: B) => A) => A) => {
const loop = (input: B) => handler(input, loop);
return loop(input);
};
}
chain<Promise<string>>()('test', async (value, loop) => {
const ready = await someExternalThing();
if (ready) {
return value;
} else {
return await loop(value + '!');
}
});
Upvotes: 1
Reputation: 15305
The following works and do what you want as far as I understand:
function chain<A, C> (input: A, handler: (input: A, loop: (input: A) => C) => C): C {
const loop = (input: A): C => handler(input, loop);
return loop(input);
}
chain<string, Promise<string>>('test', async (value, loop) => {
const ready = await someExternalThing();
if (ready) {
return value;
} else {
return await loop(value + '!');
}
})
The key being to specify the generic parameters when calling chain
.
Upvotes: 1