Reputation: 3331
I expect to be able to infer the return type of async functions when they are yielded as values from a generator.
In the example below, inference of the type of the yielded async functions seems to work, (they are detected as () => Promise<string>
). However, T
is not able to be inferred as string
and ends up unknown
.
I have reduced a more complex problem to the example below (see playground). It retains all the essential features I think I need for the real case.
The commented code indicates a bit more about the reason for needing to infer both J
and T
. Crucially, extensions to Job<T>
can carry job-specific metadata.
Can anyone work out how to compose type definitions that can infer both J
and T
in the below case, so that the settlement type is JobSettlement<string, () => Promise<string>>
instead of JobSettlement<unknown, () => Promise<string>>
I speculate this is one of the unhandled cases of microsoft/TypeScript#47599 but I am unsure how to put it into a shape where TypeScript can handle the inference.
type Job<T> = () => Promise<T>
export interface JobFulfilment<T, J extends Job<T>> {
job: J;
status: "fulfilled";
value: T;
}
export interface JobRejection<T, J extends Job<T>> {
job: J;
status: "rejected";
reason: unknown;
}
export type JobSettlement<T, J extends Job<T>> =
| JobFulfilment<T, J>
| JobRejection<T, J>;
export async function* createSettlementSequence<T, J extends Job<T>>(
createJobSequence: () => AsyncGenerator<J>
): AsyncIterable<JobSettlement<T, J>> {
for await (const job of createJobSequence()){
try{
const value = await job();
yield {
job,
value,
status:"fulfilled",
}
}
catch(reason){
yield {
job,
reason,
status:"rejected"
}
}
}
}
const settlementSequence = createSettlementSequence(async function* () {
yield async () => "foo"
yield async () => "bar"
yield async () => "baz"
})
// const settlementSequenceWithMetadata = createSettlementSequence(async function* () {
// yield Object.assign(async () => "foo", { task: 0})
// yield Object.assign(async () => "bar", { task: 1})
// yield Object.assign(async () => "baz", { task: 2})
// })
Upvotes: 0
Views: 181
Reputation: 3331
A naive attempt to simplify this to have just one generic binding (J extends Job) and to infer T
via a Fulfilment<J extends Job<unknown>>
fails as per this playground
However, this can be addressed by explicitly asserting the type via...
(await job()) as Awaited<ReturnType<typeof job>>
It seems surprising that Typescript needs to be told this, but hey.
type Job<T> = () => Promise<T>;
export interface JobFulfilment<J extends Job<unknown>> {
job: J;
status: "fulfilled";
value: Awaited<ReturnType<J>>;
}
export interface JobRejection<J extends Job<unknown>> {
job: J;
status: "rejected";
reason: any;
}
export type JobSettlement<J extends Job<unknown>> =
| JobFulfilment<J>
| JobRejection<J>;
export async function* createSettlementSequence<J extends Job<unknown>>(
createJobSequence: () => AsyncGenerator<J>,
): AsyncIterable<JobSettlement<J>> {
for await (const job of createJobSequence()) {
try {
const value = (await job()) as Awaited<ReturnType<typeof job>>;
yield {
job,
value,
status: "fulfilled",
};
} catch (reason) {
yield {
job,
reason,
status: "rejected",
};
}
}
}
const settlementSequence = createSettlementSequence(async function* () {
yield Object.assign(async () => "foo", { task: 0 });
yield Object.assign(async () => "bar", { task: 1 });
yield Object.assign(async () => "baz", { task: 2 });
});
for await (const settlement of settlementSequence) {
if (settlement.status === "fulfilled") {
const { value, job } = settlement;
console.log(`Task ${job.task} successfully returned ${value}`);
}
}
Upvotes: 0